diff options
Diffstat (limited to 'spec/ruby/core/file')
123 files changed, 5874 insertions, 0 deletions
diff --git a/spec/ruby/core/file/absolute_path_spec.rb b/spec/ruby/core/file/absolute_path_spec.rb new file mode 100644 index 0000000000..315eead34f --- /dev/null +++ b/spec/ruby/core/file/absolute_path_spec.rb @@ -0,0 +1,94 @@ +require_relative '../../spec_helper' + +describe "File.absolute_path?" do + before :each do + @abs = File.expand_path(__FILE__) + end + + it "returns true if it's an absolute pathname" do + File.absolute_path?(@abs).should be_true + end + + it "returns false if it's a relative path" do + File.absolute_path?(File.basename(__FILE__)).should be_false + end + + it "returns false if it's a tricky relative path" do + File.absolute_path?("C:foo\\bar").should be_false + end + + it "does not expand '~' to a home directory." do + File.absolute_path?('~').should be_false + end + + it "does not expand '~user' to a home directory." do + path = File.dirname(@abs) + Dir.chdir(path) do + File.absolute_path?('~user').should be_false + end + end + + it "calls #to_path on its argument" do + mock = mock_to_path(File.expand_path(__FILE__)) + + File.absolute_path?(mock).should be_true + end + + platform_is_not :windows do + it "takes into consideration the platform's root" do + File.absolute_path?("C:\\foo\\bar").should be_false + File.absolute_path?("C:/foo/bar").should be_false + File.absolute_path?("/foo/bar\\baz").should be_true + end + end + + platform_is :windows do + it "takes into consideration the platform path separator(s)" do + File.absolute_path?("C:\\foo\\bar").should be_true + File.absolute_path?("C:/foo/bar").should be_true + File.absolute_path?("/foo/bar\\baz").should be_false + end + end +end + +describe "File.absolute_path" do + before :each do + @abs = File.expand_path(__FILE__) + end + + it "returns the argument if it's an absolute pathname" do + File.absolute_path(@abs).should == @abs + end + + it "resolves paths relative to the current working directory" do + path = File.dirname(@abs) + Dir.chdir(path) do + File.absolute_path('hello.txt').should == File.join(Dir.pwd, 'hello.txt') + end + end + + it "does not expand '~' to a home directory." do + File.absolute_path('~').should_not == File.expand_path('~') + end + + platform_is_not :windows do + it "does not expand '~' when given dir argument" do + File.absolute_path('~', '/').should == '/~' + end + end + + it "does not expand '~user' to a home directory." do + path = File.dirname(@abs) + Dir.chdir(path) do + File.absolute_path('~user').should == File.join(Dir.pwd, '~user') + end + end + + it "accepts a second argument of a directory from which to resolve the path" do + File.absolute_path(__FILE__, __dir__).should == @abs + end + + it "calls #to_path on its argument" do + File.absolute_path(mock_to_path(@abs)).should == @abs + end +end diff --git a/spec/ruby/core/file/atime_spec.rb b/spec/ruby/core/file/atime_spec.rb new file mode 100644 index 0000000000..e47e70e5ac --- /dev/null +++ b/spec/ruby/core/file/atime_spec.rb @@ -0,0 +1,60 @@ +require_relative '../../spec_helper' + +describe "File.atime" do + before :each do + @file = tmp('test.txt') + touch @file + end + + after :each do + rm_r @file + end + + it "returns the last access time for the named file as a Time object" do + File.atime(@file) + File.atime(@file).should be_kind_of(Time) + end + + platform_is :linux, :windows do + unless ENV.key?('TRAVIS') # https://bugs.ruby-lang.org/issues/17926 + ## NOTE also that some Linux systems disable atime (e.g. via mount params) for better filesystem speed. + it "returns the last access time for the named file with microseconds" do + supports_subseconds = Integer(`stat -c%x '#{__FILE__}'`[/\.(\d{1,6})/, 1], 10) + if supports_subseconds != 0 + expected_time = Time.at(Time.now.to_i + 0.123456) + File.utime expected_time, 0, @file + File.atime(@file).usec.should == expected_time.usec + else + File.atime(__FILE__).usec.should == 0 + end + rescue Errno::ENOENT => e + # Native Windows don't have stat command. + skip e.message + end + end + end + + it "raises an Errno::ENOENT exception if the file is not found" do + -> { File.atime('a_fake_file') }.should raise_error(Errno::ENOENT) + end + + it "accepts an object that has a #to_path method" do + File.atime(mock_to_path(@file)) + end +end + +describe "File#atime" do + before :each do + @name = File.expand_path(__FILE__) + @file = File.open(@name) + end + + after :each do + @file.close rescue nil + end + + it "returns the last access time to self" do + @file.atime + @file.atime.should be_kind_of(Time) + end +end diff --git a/spec/ruby/core/file/basename_spec.rb b/spec/ruby/core/file/basename_spec.rb new file mode 100644 index 0000000000..989409d76b --- /dev/null +++ b/spec/ruby/core/file/basename_spec.rb @@ -0,0 +1,183 @@ +# -*- encoding: utf-8 -*- +require_relative '../../spec_helper' + +# TODO: Fix these +describe "File.basename" do + it "returns the basename of a path (basic cases)" do + File.basename("/Some/path/to/test.txt").should == "test.txt" + File.basename(File.join("/tmp")).should == "tmp" + File.basename(File.join(*%w( g f d s a b))).should == "b" + File.basename("/tmp", ".*").should == "tmp" + File.basename("/tmp", ".c").should == "tmp" + File.basename("/tmp.c", ".c").should == "tmp" + File.basename("/tmp.c", ".*").should == "tmp" + File.basename("/tmp.c", ".?").should == "tmp.c" + File.basename("/tmp.cpp", ".*").should == "tmp" + File.basename("/tmp.cpp", ".???").should == "tmp.cpp" + File.basename("/tmp.o", ".c").should == "tmp.o" + File.basename(File.join("/tmp/")).should == "tmp" + File.basename("/").should == "/" + File.basename("//").should == "/" + File.basename("dir///base", ".*").should == "base" + File.basename("dir///base", ".c").should == "base" + File.basename("dir///base.c", ".c").should == "base" + File.basename("dir///base.c", ".*").should == "base" + File.basename("dir///base.o", ".c").should == "base.o" + File.basename("dir///base///").should == "base" + File.basename("dir//base/", ".*").should == "base" + File.basename("dir//base/", ".c").should == "base" + File.basename("dir//base.c/", ".c").should == "base" + File.basename("dir//base.c/", ".*").should == "base" + end + + it "returns the last component of the filename" do + File.basename('a').should == 'a' + File.basename('/a').should == 'a' + File.basename('/a/b').should == 'b' + File.basename('/ab/ba/bag').should == 'bag' + File.basename('/ab/ba/bag.txt').should == 'bag.txt' + File.basename('/').should == '/' + File.basename('/foo/bar/baz.rb', '.rb').should == 'baz' + File.basename('baz.rb', 'z.rb').should == 'ba' + end + + it "returns an string" do + File.basename("foo").should be_kind_of(String) + end + + it "returns the basename for unix format" do + File.basename("/foo/bar").should == "bar" + File.basename("/foo/bar.txt").should == "bar.txt" + File.basename("bar.c").should == "bar.c" + File.basename("/bar").should == "bar" + File.basename("/bar/").should == "bar" + + # Considered UNC paths on Windows + platform_is :windows do + File.basename("baz//foo").should =="foo" + File.basename("//foo/bar/baz").should == "baz" + end + end + + it "returns the basename for edge cases" do + File.basename("").should == "" + File.basename(".").should == "." + File.basename("..").should == ".." + platform_is_not :windows do + File.basename("//foo/").should == "foo" + File.basename("//foo//").should == "foo" + end + File.basename("foo/").should == "foo" + end + + it "ignores a trailing directory separator" do + File.basename("foo.rb/", '.rb').should == "foo" + File.basename("bar.rb///", '.*').should == "bar" + end + + it "returns the basename for unix suffix" do + File.basename("bar.c", ".c").should == "bar" + File.basename("bar.txt", ".txt").should == "bar" + File.basename("/bar.txt", ".txt").should == "bar" + File.basename("/foo/bar.txt", ".txt").should == "bar" + File.basename("bar.txt", ".exe").should == "bar.txt" + File.basename("bar.txt.exe", ".exe").should == "bar.txt" + File.basename("bar.txt.exe", ".txt").should == "bar.txt.exe" + File.basename("bar.txt", ".*").should == "bar" + File.basename("bar.txt.exe", ".*").should == "bar.txt" + File.basename("bar.txt.exe", ".txt.exe").should == "bar" + end + + platform_is_not :windows do + it "takes into consideration the platform path separator(s)" do + File.basename("C:\\foo\\bar").should == "C:\\foo\\bar" + File.basename("C:/foo/bar").should == "bar" + File.basename("/foo/bar\\baz").should == "bar\\baz" + end + end + + platform_is :windows do + it "takes into consideration the platform path separator(s)" do + File.basename("C:\\foo\\bar").should == "bar" + File.basename("C:/foo/bar").should == "bar" + File.basename("/foo/bar\\baz").should == "baz" + end + end + + it "raises a TypeError if the arguments are not String types" do + -> { File.basename(nil) }.should raise_error(TypeError) + -> { File.basename(1) }.should raise_error(TypeError) + -> { File.basename("bar.txt", 1) }.should raise_error(TypeError) + -> { File.basename(true) }.should raise_error(TypeError) + end + + it "accepts an object that has a #to_path method" do + File.basename(mock_to_path("foo.txt")) + end + + it "raises an ArgumentError if passed more than two arguments" do + -> { File.basename('bar.txt', '.txt', '.txt') }.should raise_error(ArgumentError) + end + + # specific to MS Windows + platform_is :windows do + it "returns the basename for windows" do + File.basename("C:\\foo\\bar\\baz.txt").should == "baz.txt" + File.basename("C:\\foo\\bar").should == "bar" + File.basename("C:\\foo\\bar\\").should == "bar" + File.basename("C:\\foo").should == "foo" + File.basename("C:\\").should == "\\" + end + + it "returns basename windows unc" do + File.basename("\\\\foo\\bar\\baz.txt").should == "baz.txt" + File.basename("\\\\foo\\bar\\baz").should =="baz" + end + + it "returns basename windows forward slash" do + File.basename("C:/").should == "/" + File.basename("C:/foo").should == "foo" + File.basename("C:/foo/bar").should == "bar" + File.basename("C:/foo/bar/").should == "bar" + File.basename("C:/foo/bar//").should == "bar" + end + + it "returns basename with windows suffix" do + File.basename("c:\\bar.txt", ".txt").should == "bar" + File.basename("c:\\foo\\bar.txt", ".txt").should == "bar" + File.basename("c:\\bar.txt", ".exe").should == "bar.txt" + File.basename("c:\\bar.txt.exe", ".exe").should == "bar.txt" + File.basename("c:\\bar.txt.exe", ".txt").should == "bar.txt.exe" + File.basename("c:\\bar.txt", ".*").should == "bar" + File.basename("c:\\bar.txt.exe", ".*").should == "bar.txt" + end + end + + + it "returns the extension for a multibyte filename" do + File.basename('/path/Офис.m4a').should == "Офис.m4a" + end + + it "returns the basename with the same encoding as the original" do + basename = File.basename('C:/Users/Scuby Pagrubý'.encode(Encoding::Windows_1250)) + basename.should == 'Scuby Pagrubý'.encode(Encoding::Windows_1250) + basename.encoding.should == Encoding::Windows_1250 + end + + it "returns a new unfrozen String" do + exts = [nil, '.rb', '.*', '.txt'] + ['foo.rb','//', '/test/', 'test'].each do |example| + exts.each do |ext| + original = example.freeze + result = if ext + File.basename(original, ext) + else + File.basename(original) + end + result.should_not equal(original) + result.frozen?.should == false + end + end + end + +end diff --git a/spec/ruby/core/file/birthtime_spec.rb b/spec/ruby/core/file/birthtime_spec.rb new file mode 100644 index 0000000000..f82eaf7cca --- /dev/null +++ b/spec/ruby/core/file/birthtime_spec.rb @@ -0,0 +1,56 @@ +require_relative '../../spec_helper' + +platform_is :windows, :darwin, :freebsd, :netbsd, :linux do + not_implemented_messages = [ + "birthtime() function is unimplemented", # unsupported OS/version + "birthtime is unimplemented", # unsupported filesystem + ] + + describe "File.birthtime" do + before :each do + @file = __FILE__ + end + + after :each do + @file = nil + end + + it "returns the birth time for the named file as a Time object" do + File.birthtime(@file) + File.birthtime(@file).should be_kind_of(Time) + rescue NotImplementedError => e + e.message.should.start_with?(*not_implemented_messages) + end + + it "accepts an object that has a #to_path method" do + File.birthtime(@file) # Avoid to failure of mock object with old Kernel and glibc + File.birthtime(mock_to_path(@file)) + rescue NotImplementedError => e + e.message.should.start_with?(*not_implemented_messages) + end + + it "raises an Errno::ENOENT exception if the file is not found" do + -> { File.birthtime('bogus') }.should raise_error(Errno::ENOENT) + rescue NotImplementedError => e + e.message.should.start_with?(*not_implemented_messages) + end + end + + describe "File#birthtime" do + before :each do + @file = File.open(__FILE__) + end + + after :each do + @file.close + @file = nil + end + + it "returns the birth time for self" do + @file.birthtime + @file.birthtime.should be_kind_of(Time) + rescue NotImplementedError => e + e.message.should.start_with?(*not_implemented_messages) + end + end +end diff --git a/spec/ruby/core/file/blockdev_spec.rb b/spec/ruby/core/file/blockdev_spec.rb new file mode 100644 index 0000000000..9ba9afc251 --- /dev/null +++ b/spec/ruby/core/file/blockdev_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative '../../shared/file/blockdev' + +describe "File.blockdev?" do + it_behaves_like :file_blockdev, :blockdev?, File +end diff --git a/spec/ruby/core/file/chardev_spec.rb b/spec/ruby/core/file/chardev_spec.rb new file mode 100644 index 0000000000..1fc932ee4e --- /dev/null +++ b/spec/ruby/core/file/chardev_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative '../../shared/file/chardev' + +describe "File.chardev?" do + it_behaves_like :file_chardev, :chardev?, File +end diff --git a/spec/ruby/core/file/chmod_spec.rb b/spec/ruby/core/file/chmod_spec.rb new file mode 100644 index 0000000000..5ca15c9748 --- /dev/null +++ b/spec/ruby/core/file/chmod_spec.rb @@ -0,0 +1,185 @@ +require_relative '../../spec_helper' + +describe "File#chmod" do + before :each do + @filename = tmp('i_exist.exe') + @file = File.open(@filename, 'w') + end + + after :each do + @file.close + rm_r @filename + end + + it "returns 0 if successful" do + @file.chmod(0755).should == 0 + end + + it "raises RangeError with too large values" do + -> { @file.chmod(2**64) }.should raise_error(RangeError) + -> { @file.chmod(-2**63 - 1) }.should raise_error(RangeError) + end + + it "invokes to_int on non-integer argument" do + mode = File.stat(@filename).mode + (obj = mock('mode')).should_receive(:to_int).and_return(mode) + @file.chmod(obj) + File.stat(@filename).mode.should == mode + end + + platform_is :windows do + it "with '0444' makes file readable and executable but not writable" do + @file.chmod(0444) + File.readable?(@filename).should == true + File.writable?(@filename).should == false + File.executable?(@filename).should == true + end + + it "with '0644' makes file readable and writable and also executable" do + @file.chmod(0644) + File.readable?(@filename).should == true + File.writable?(@filename).should == true + File.executable?(@filename).should == true + end + end + + platform_is_not :windows do + as_user do + it "with '0222' makes file writable but not readable or executable" do + @file.chmod(0222) + File.readable?(@filename).should == false + File.writable?(@filename).should == true + File.executable?(@filename).should == false + end + + it "with '0444' makes file readable but not writable or executable" do + @file.chmod(0444) + File.readable?(@filename).should == true + File.writable?(@filename).should == false + File.executable?(@filename).should == false + end + + it "with '0666' makes file readable and writable but not executable" do + @file.chmod(0666) + File.readable?(@filename).should == true + File.writable?(@filename).should == true + File.executable?(@filename).should == false + end + + it "with '0111' makes file executable but not readable or writable" do + @file.chmod(0111) + File.readable?(@filename).should == false + File.writable?(@filename).should == false + File.executable?(@filename).should == true + end + + it "modifies the permission bits of the files specified" do + @file.chmod(0755) + File.stat(@filename).mode.should == 33261 + end + end + end +end + +describe "File.chmod" do + before :each do + @file = tmp('i_exist.exe') + touch @file + @count = File.chmod(0755, @file) + end + + after :each do + rm_r @file + end + + it "returns the number of files modified" do + @count.should == 1 + end + + it "raises RangeError with too large values" do + -> { File.chmod(2**64, @file) }.should raise_error(RangeError) + -> { File.chmod(-2**63 - 1, @file) }.should raise_error(RangeError) + end + + it "accepts an object that has a #to_path method" do + File.chmod(0, mock_to_path(@file)) + end + + it "throws a TypeError if the given path is not coercible into a string" do + -> { File.chmod(0, []) }.should raise_error(TypeError) + end + + it "raises an error for a non existent path" do + -> { + File.chmod(0644, "#{@file}.not.existing") + }.should raise_error(Errno::ENOENT) + end + + it "invokes to_int on non-integer argument" do + mode = File.stat(@file).mode + (obj = mock('mode')).should_receive(:to_int).and_return(mode) + File.chmod(obj, @file) + File.stat(@file).mode.should == mode + end + + it "invokes to_str on non-string file names" do + mode = File.stat(@file).mode + (obj = mock('path')).should_receive(:to_str).and_return(@file) + File.chmod(mode, obj) + File.stat(@file).mode.should == mode + end + + platform_is :windows do + it "with '0444' makes file readable and executable but not writable" do + File.chmod(0444, @file) + File.readable?(@file).should == true + File.writable?(@file).should == false + File.executable?(@file).should == true + end + + it "with '0644' makes file readable and writable and also executable" do + File.chmod(0644, @file) + File.readable?(@file).should == true + File.writable?(@file).should == true + File.executable?(@file).should == true + end + end + + platform_is_not :windows do + as_user do + it "with '0222' makes file writable but not readable or executable" do + File.chmod(0222, @file) + File.readable?(@file).should == false + File.writable?(@file).should == true + File.executable?(@file).should == false + end + + it "with '0444' makes file readable but not writable or executable" do + File.chmod(0444, @file) + File.readable?(@file).should == true + File.writable?(@file).should == false + File.executable?(@file).should == false + end + end + + it "with '0666' makes file readable and writable but not executable" do + File.chmod(0666, @file) + File.readable?(@file).should == true + File.writable?(@file).should == true + File.executable?(@file).should == false + end + + as_user do + it "with '0111' makes file executable but not readable or writable" do + File.chmod(0111, @file) + File.readable?(@file).should == false + File.writable?(@file).should == false + File.executable?(@file).should == true + end + end + + it "modifies the permission bits of the files specified" do + File.stat(@file).mode.should == 33261 + end + end +end diff --git a/spec/ruby/core/file/chown_spec.rb b/spec/ruby/core/file/chown_spec.rb new file mode 100644 index 0000000000..4db0e3712c --- /dev/null +++ b/spec/ruby/core/file/chown_spec.rb @@ -0,0 +1,144 @@ +require_relative '../../spec_helper' + +describe "File.chown" do + before :each do + @fname = tmp('file_chown_test') + touch @fname + end + + after :each do + rm_r @fname + end + + as_superuser do + platform_is :windows do + it "does not modify the owner id of the file" do + File.chown 0, nil, @fname + File.stat(@fname).uid.should == 0 + File.chown 501, nil, @fname + File.stat(@fname).uid.should == 0 + end + + it "does not modify the group id of the file" do + File.chown nil, 0, @fname + File.stat(@fname).gid.should == 0 + File.chown nil, 501, @fname + File.stat(@fname).gid.should == 0 + end + end + + platform_is_not :windows do + it "changes the owner id of the file" do + File.chown 501, nil, @fname + File.stat(@fname).uid.should == 501 + File.chown 0, nil, @fname + File.stat(@fname).uid.should == 0 + end + + it "changes the group id of the file" do + File.chown nil, 501, @fname + File.stat(@fname).gid.should == 501 + File.chown nil, 0, @fname + File.stat(@fname).uid.should == 0 + end + + it "does not modify the owner id of the file if passed nil or -1" do + File.chown 501, nil, @fname + File.chown nil, nil, @fname + File.stat(@fname).uid.should == 501 + File.chown nil, -1, @fname + File.stat(@fname).uid.should == 501 + end + + it "does not modify the group id of the file if passed nil or -1" do + File.chown nil, 501, @fname + File.chown nil, nil, @fname + File.stat(@fname).gid.should == 501 + File.chown nil, -1, @fname + File.stat(@fname).gid.should == 501 + end + end + end + + it "returns the number of files processed" do + File.chown(nil, nil, @fname, @fname).should == 2 + end + + platform_is_not :windows do + it "raises an error for a non existent path" do + -> { + File.chown(nil, nil, "#{@fname}_not_existing") + }.should raise_error(Errno::ENOENT) + end + end + + it "accepts an object that has a #to_path method" do + File.chown(nil, nil, mock_to_path(@fname)).should == 1 + end +end + +describe "File#chown" do + before :each do + @fname = tmp('file_chown_test') + @file = File.open(@fname, 'w') + end + + after :each do + @file.close unless @file.closed? + rm_r @fname + end + + as_superuser do + platform_is :windows do + it "does not modify the owner id of the file" do + @file.chown 0, nil + @file.stat.uid.should == 0 + @file.chown 501, nil + @file.stat.uid.should == 0 + end + + it "does not modify the group id of the file" do + @file.chown nil, 0 + @file.stat.gid.should == 0 + @file.chown nil, 501 + @file.stat.gid.should == 0 + end + end + + platform_is_not :windows do + it "changes the owner id of the file" do + @file.chown 501, nil + @file.stat.uid.should == 501 + @file.chown 0, nil + @file.stat.uid.should == 0 + end + + it "changes the group id of the file" do + @file.chown nil, 501 + @file.stat.gid.should == 501 + @file.chown nil, 0 + @file.stat.uid.should == 0 + end + + it "does not modify the owner id of the file if passed nil or -1" do + @file.chown 501, nil + @file.chown nil, nil + @file.stat.uid.should == 501 + @file.chown nil, -1 + @file.stat.uid.should == 501 + end + + it "does not modify the group id of the file if passed nil or -1" do + @file.chown nil, 501 + @file.chown nil, nil + @file.stat.gid.should == 501 + @file.chown nil, -1 + @file.stat.gid.should == 501 + end + end + end + + it "returns 0" do + @file.chown(nil, nil).should == 0 + end +end diff --git a/spec/ruby/core/file/constants/constants_spec.rb b/spec/ruby/core/file/constants/constants_spec.rb new file mode 100644 index 0000000000..bba248c21e --- /dev/null +++ b/spec/ruby/core/file/constants/constants_spec.rb @@ -0,0 +1,31 @@ +require_relative '../../../spec_helper' + +["APPEND", "CREAT", "EXCL", "FNM_CASEFOLD", + "FNM_DOTMATCH", "FNM_EXTGLOB", "FNM_NOESCAPE", "FNM_PATHNAME", + "FNM_SYSCASE", "LOCK_EX", "LOCK_NB", "LOCK_SH", + "LOCK_UN", "NONBLOCK", "RDONLY", + "RDWR", "TRUNC", "WRONLY", "SHARE_DELETE"].each do |const| + describe "File::Constants::#{const}" do + it "is defined" do + File::Constants.const_defined?(const).should be_true + end + end +end + +platform_is :windows do + describe "File::Constants::BINARY" do + it "is defined" do + File::Constants.const_defined?(:BINARY).should be_true + end + end +end + +platform_is_not :windows do + ["NOCTTY", "SYNC"].each do |const| + describe "File::Constants::#{const}" do + it "is defined" do + File::Constants.const_defined?(const).should be_true + end + end + end +end diff --git a/spec/ruby/core/file/constants_spec.rb b/spec/ruby/core/file/constants_spec.rb new file mode 100644 index 0000000000..5f058a7f40 --- /dev/null +++ b/spec/ruby/core/file/constants_spec.rb @@ -0,0 +1,141 @@ +require_relative '../../spec_helper' + +# TODO: migrate these to constants/constants_spec.rb + +describe "File::Constants" do + it "matches mode constants" do + File::FNM_NOESCAPE.should_not == nil + File::FNM_PATHNAME.should_not == nil + File::FNM_DOTMATCH.should_not == nil + File::FNM_CASEFOLD.should_not == nil + File::FNM_SYSCASE.should_not == nil + + platform_is :windows do #|| VMS + File::FNM_SYSCASE.should == 8 + end + end + + # Only these constants are not inherited from the IO class + it "the separator constant" do + File::SEPARATOR.should_not == nil + File::Separator.should_not == nil + File::PATH_SEPARATOR.should_not == nil + File::SEPARATOR.should == "/" + + platform_is :windows do #|| VMS + File::ALT_SEPARATOR.should_not == nil + File::PATH_SEPARATOR.should == ";" + end + + platform_is_not :windows do + File::ALT_SEPARATOR.should == nil + File::PATH_SEPARATOR.should == ":" + end + end + + it "the open mode constants" do + File::APPEND.should_not == nil + File::CREAT.should_not == nil + File::EXCL.should_not == nil + File::NONBLOCK.should_not == nil + File::RDONLY.should_not == nil + File::RDWR.should_not == nil + File::TRUNC.should_not == nil + File::WRONLY.should_not == nil + + platform_is_not :windows do # Not sure about VMS here + File::NOCTTY.should_not == nil + end + end + + it "lock mode constants" do + File::LOCK_EX.should_not == nil + File::LOCK_NB.should_not == nil + File::LOCK_SH.should_not == nil + File::LOCK_UN.should_not == nil + end +end + +describe "File::Constants" do + # These mode and permission bits are platform dependent + it "File::RDONLY" do + defined?(File::RDONLY).should == "constant" + end + + it "File::WRONLY" do + defined?(File::WRONLY).should == "constant" + end + + it "File::CREAT" do + defined?(File::CREAT).should == "constant" + end + + it "File::RDWR" do + defined?(File::RDWR).should == "constant" + end + + it "File::APPEND" do + defined?(File::APPEND).should == "constant" + end + + it "File::TRUNC" do + defined?(File::TRUNC).should == "constant" + end + + platform_is_not :windows do # Not sure about VMS here + it "File::NOCTTY" do + defined?(File::NOCTTY).should == "constant" + end + end + + it "File::NONBLOCK" do + defined?(File::NONBLOCK).should == "constant" + end + + it "File::LOCK_EX" do + defined?(File::LOCK_EX).should == "constant" + end + + it "File::LOCK_NB" do + defined?(File::LOCK_NB).should == "constant" + end + + it "File::LOCK_SH" do + defined?(File::LOCK_SH).should == "constant" + end + + it "File::LOCK_UN" do + defined?(File::LOCK_UN).should == "constant" + end + + it "File::SEPARATOR" do + defined?(File::SEPARATOR).should == "constant" + end + it "File::Separator" do + defined?(File::Separator).should == "constant" + end + + it "File::PATH_SEPARATOR" do + defined?(File::PATH_SEPARATOR).should == "constant" + end + + it "File::SEPARATOR" do + defined?(File::SEPARATOR).should == "constant" + File::SEPARATOR.should == "/" + end + + platform_is :windows do #|| VMS + it "File::ALT_SEPARATOR" do + defined?(File::ALT_SEPARATOR).should == "constant" + File::PATH_SEPARATOR.should == ";" + end + end + + platform_is_not :windows do + it "File::PATH_SEPARATOR" do + defined?(File::PATH_SEPARATOR).should == "constant" + File::PATH_SEPARATOR.should == ":" + end + end + +end diff --git a/spec/ruby/core/file/ctime_spec.rb b/spec/ruby/core/file/ctime_spec.rb new file mode 100644 index 0000000000..718f26d5cc --- /dev/null +++ b/spec/ruby/core/file/ctime_spec.rb @@ -0,0 +1,54 @@ +require_relative '../../spec_helper' + +describe "File.ctime" do + before :each do + @file = __FILE__ + end + + after :each do + @file = nil + end + + it "returns the change time for the named file (the time at which directory information about the file was changed, not the file itself)." do + File.ctime(@file) + File.ctime(@file).should be_kind_of(Time) + end + + platform_is :linux, :windows do + it "returns the change time for the named file (the time at which directory information about the file was changed, not the file itself) with microseconds." do + supports_subseconds = Integer(`stat -c%z '#{__FILE__}'`[/\.(\d{1,6})/, 1], 10) + if supports_subseconds != 0 + File.ctime(__FILE__).usec.should > 0 + else + File.ctime(__FILE__).usec.should == 0 + end + rescue Errno::ENOENT => e + # Windows don't have stat command. + skip e.message + end + end + + it "accepts an object that has a #to_path method" do + File.ctime(mock_to_path(@file)) + end + + it "raises an Errno::ENOENT exception if the file is not found" do + -> { File.ctime('bogus') }.should raise_error(Errno::ENOENT) + end +end + +describe "File#ctime" do + before :each do + @file = File.open(__FILE__) + end + + after :each do + @file.close + @file = nil + end + + it "returns the change time for the named file (the time at which directory information about the file was changed, not the file itself)." do + @file.ctime + @file.ctime.should be_kind_of(Time) + end +end diff --git a/spec/ruby/core/file/delete_spec.rb b/spec/ruby/core/file/delete_spec.rb new file mode 100644 index 0000000000..4098499942 --- /dev/null +++ b/spec/ruby/core/file/delete_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/unlink' + +describe "File.delete" do + it_behaves_like :file_unlink, :delete +end diff --git a/spec/ruby/core/file/directory_spec.rb b/spec/ruby/core/file/directory_spec.rb new file mode 100644 index 0000000000..8014a7a03d --- /dev/null +++ b/spec/ruby/core/file/directory_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' +require_relative '../../shared/file/directory' + +describe "File.directory?" do + it_behaves_like :file_directory, :directory?, File +end + +describe "File.directory?" do + it_behaves_like :file_directory_io, :directory?, File +end diff --git a/spec/ruby/core/file/dirname_spec.rb b/spec/ruby/core/file/dirname_spec.rb new file mode 100644 index 0000000000..8e6016ce6f --- /dev/null +++ b/spec/ruby/core/file/dirname_spec.rb @@ -0,0 +1,137 @@ +require_relative '../../spec_helper' + +describe "File.dirname" do + it "returns all the components of filename except the last one" do + File.dirname('/home/jason').should == '/home' + File.dirname('/home/jason/poot.txt').should == '/home/jason' + File.dirname('poot.txt').should == '.' + File.dirname('/holy///schnikies//w00t.bin').should == '/holy///schnikies' + File.dirname('').should == '.' + File.dirname('/').should == '/' + File.dirname('/foo/foo').should == '/foo' + end + + context "when level is passed" do + it "returns all the components of filename except the last parts by the level" do + File.dirname('/home/jason', 2).should == '/' + File.dirname('/home/jason/poot.txt', 2).should == '/home' + end + + it "returns the same String if the level is 0" do + File.dirname('poot.txt', 0).should == 'poot.txt' + File.dirname('/', 0).should == '/' + end + + it "raises ArgumentError if the level is negative" do + -> { + File.dirname('/home/jason', -1) + }.should raise_error(ArgumentError, "negative level: -1") + end + + it "returns '/' when level exceeds the number of segments in the path" do + File.dirname("/home/jason", 100).should == '/' + end + + it "calls #to_int if passed not numeric value" do + object = Object.new + def object.to_int; 2; end + + File.dirname("/a/b/c/d", object).should == '/a/b' + end + end + + it "returns a String" do + File.dirname("foo").should be_kind_of(String) + end + + it "does not modify its argument" do + x = "/usr/bin" + File.dirname(x) + x.should == "/usr/bin" + end + + it "ignores a trailing /" do + File.dirname("/foo/bar/").should == "/foo" + end + + it "returns the return all the components of filename except the last one (unix format)" do + File.dirname("foo").should =="." + File.dirname("/foo").should =="/" + File.dirname("/foo/bar").should =="/foo" + File.dirname("/foo/bar.txt").should =="/foo" + File.dirname("/foo/bar/baz").should =="/foo/bar" + end + + it "returns all the components of filename except the last one (edge cases on all platforms)" do + File.dirname("").should == "." + File.dirname(".").should == "." + File.dirname("./").should == "." + File.dirname("./b/./").should == "./b" + File.dirname("..").should == "." + File.dirname("../").should == "." + File.dirname("/").should == "/" + File.dirname("/.").should == "/" + File.dirname("/foo/").should == "/" + File.dirname("/foo/.").should == "/foo" + File.dirname("/foo/./").should == "/foo" + File.dirname("/foo/../.").should == "/foo/.." + File.dirname("foo/../").should == "foo" + end + + platform_is_not :windows do + it "returns all the components of filename except the last one (edge cases on non-windows)" do + File.dirname('/////').should == '/' + File.dirname("//foo//").should == "/" + File.dirname('foo\bar').should == '.' + File.dirname('/foo\bar').should == '/' + File.dirname('foo/bar\baz').should == 'foo' + end + end + + platform_is :windows do + it "returns all the components of filename except the last one (edge cases on windows)" do + File.dirname("//foo").should == "//foo" + File.dirname("//foo//").should == "//foo" + File.dirname('/////').should == '//' + end + end + + it "accepts an object that has a #to_path method" do + File.dirname(mock_to_path("/")).should == "/" + end + + it "raises a TypeError if not passed a String type" do + -> { File.dirname(nil) }.should raise_error(TypeError) + -> { File.dirname(0) }.should raise_error(TypeError) + -> { File.dirname(true) }.should raise_error(TypeError) + -> { File.dirname(false) }.should raise_error(TypeError) + end + + # Windows specific tests + platform_is :windows do + it "returns the return all the components of filename except the last one (Windows format)" do + File.dirname("C:\\foo\\bar\\baz.txt").should =="C:\\foo\\bar" + File.dirname("C:\\foo\\bar").should =="C:\\foo" + File.dirname("C:\\foo\\bar\\").should == "C:\\foo" + File.dirname("C:\\foo").should == "C:\\" + File.dirname("C:\\").should =="C:\\" + end + + it "returns the return all the components of filename except the last one (windows unc)" do + File.dirname("\\\\foo\\bar\\baz.txt").should == "\\\\foo\\bar" + File.dirname("\\\\foo\\bar\\baz").should == "\\\\foo\\bar" + File.dirname("\\\\foo").should =="\\\\foo" + File.dirname("\\\\foo\\bar").should =="\\\\foo\\bar" + File.dirname("\\\\\\foo\\bar").should =="\\\\foo\\bar" + File.dirname("\\\\\\foo").should =="\\\\foo" + end + + it "returns the return all the components of filename except the last one (forward_slash)" do + File.dirname("C:/").should == "C:/" + File.dirname("C:/foo").should == "C:/" + File.dirname("C:/foo/bar").should == "C:/foo" + File.dirname("C:/foo/bar/").should == "C:/foo" + File.dirname("C:/foo/bar//").should == "C:/foo" + end + end +end diff --git a/spec/ruby/core/file/empty_spec.rb b/spec/ruby/core/file/empty_spec.rb new file mode 100644 index 0000000000..e8c9941676 --- /dev/null +++ b/spec/ruby/core/file/empty_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative '../../shared/file/zero' + +describe "File.empty?" do + it_behaves_like :file_zero, :empty?, File + it_behaves_like :file_zero_missing, :empty?, File +end diff --git a/spec/ruby/core/file/executable_real_spec.rb b/spec/ruby/core/file/executable_real_spec.rb new file mode 100644 index 0000000000..0cb848b201 --- /dev/null +++ b/spec/ruby/core/file/executable_real_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative '../../shared/file/executable_real' + +describe "File.executable_real?" do + it_behaves_like :file_executable_real, :executable_real?, File + it_behaves_like :file_executable_real_missing, :executable_real?, File +end diff --git a/spec/ruby/core/file/executable_spec.rb b/spec/ruby/core/file/executable_spec.rb new file mode 100644 index 0000000000..1dbb3b233d --- /dev/null +++ b/spec/ruby/core/file/executable_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative '../../shared/file/executable' + +describe "File.executable?" do + it_behaves_like :file_executable, :executable?, File + it_behaves_like :file_executable_missing, :executable?, File +end diff --git a/spec/ruby/core/file/exist_spec.rb b/spec/ruby/core/file/exist_spec.rb new file mode 100644 index 0000000000..b5600e5b07 --- /dev/null +++ b/spec/ruby/core/file/exist_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require_relative '../../shared/file/exist' + +describe "File.exist?" do + it_behaves_like :file_exist, :exist?, File +end + +describe "File.exists?" do + it "has been removed" do + File.should_not.respond_to?(:exists?) + end +end diff --git a/spec/ruby/core/file/expand_path_spec.rb b/spec/ruby/core/file/expand_path_spec.rb new file mode 100644 index 0000000000..1abcf93900 --- /dev/null +++ b/spec/ruby/core/file/expand_path_spec.rb @@ -0,0 +1,265 @@ +# -*- encoding: utf-8 -*- + +require_relative '../../spec_helper' +require_relative 'fixtures/common' +require 'etc' + +describe "File.expand_path" do + before :each do + platform_is :windows do + @base = `cd`.chomp.tr '\\', '/' + @tmpdir = "c:/tmp" + @rootdir = "c:/" + end + + platform_is_not :windows do + @base = Dir.pwd + @tmpdir = "/tmp" + @rootdir = "/" + end + end + + before :each do + @external = Encoding.default_external + end + + after :each do + Encoding.default_external = @external + end + + it "converts a pathname to an absolute pathname" do + File.expand_path('').should == @base + File.expand_path('a').should == File.join(@base, 'a') + File.expand_path('a', nil).should == File.join(@base, 'a') + end + + it "converts a pathname to an absolute pathname, Ruby-Talk:18512" do + # See Ruby-Talk:18512 + File.expand_path('.a').should == File.join(@base, '.a') + File.expand_path('..a').should == File.join(@base, '..a') + File.expand_path('a../b').should == File.join(@base, 'a../b') + end + + platform_is_not :windows do + it "keeps trailing dots on absolute pathname" do + # See Ruby-Talk:18512 + File.expand_path('a.').should == File.join(@base, 'a.') + File.expand_path('a..').should == File.join(@base, 'a..') + end + end + + it "converts a pathname to an absolute pathname, using a complete path" do + File.expand_path("", "#{@tmpdir}").should == "#{@tmpdir}" + File.expand_path("a", "#{@tmpdir}").should =="#{@tmpdir}/a" + File.expand_path("../a", "#{@tmpdir}/xxx").should == "#{@tmpdir}/a" + File.expand_path(".", "#{@rootdir}").should == "#{@rootdir}" + end + + platform_is_not :windows do + before do + @var_home = ENV['HOME'].chomp('/') + @db_home = Dir.home(ENV['USER']) + end + + # FIXME: these are insane! + it "expand path with" do + File.expand_path("../../bin", "/tmp/x").should == "/bin" + File.expand_path("../../bin", "/tmp").should == "/bin" + File.expand_path("../../bin", "/").should == "/bin" + File.expand_path("../bin", "tmp/x").should == File.join(@base, 'tmp', 'bin') + File.expand_path("../bin", "x/../tmp").should == File.join(@base, 'bin') + end + + it "expand_path for common unix path gives a full path" do + File.expand_path('/tmp/').should =='/tmp' + File.expand_path('/tmp/../../../tmp').should == '/tmp' + File.expand_path('').should == Dir.pwd + File.expand_path('./////').should == Dir.pwd + File.expand_path('.').should == Dir.pwd + File.expand_path(Dir.pwd).should == Dir.pwd + File.expand_path('~/').should == @var_home + File.expand_path('~/..badfilename').should == "#{@var_home}/..badfilename" + File.expand_path('~/a','~/b').should == "#{@var_home}/a" + File.expand_path('..').should == File.dirname(Dir.pwd) + end + + it "does not replace multiple '/' at the beginning of the path" do + File.expand_path('////some/path').should == "////some/path" + end + + it "replaces multiple '/' with a single '/'" do + File.expand_path('/some////path').should == "/some/path" + end + + it "raises an ArgumentError if the path is not valid" do + -> { File.expand_path("~a_not_existing_user") }.should raise_error(ArgumentError) + end + + it "expands ~ENV['USER'] to the user's home directory" do + File.expand_path("~#{ENV['USER']}").should == @db_home + end + + it "expands ~ENV['USER']/a to a in the user's home directory" do + File.expand_path("~#{ENV['USER']}/a").should == "#{@db_home}/a" + end + + it "does not expand ~ENV['USER'] when it's not at the start" do + File.expand_path("/~#{ENV['USER']}/a").should == "/~#{ENV['USER']}/a" + end + + it "expands ../foo with ~/dir as base dir to /path/to/user/home/foo" do + File.expand_path('../foo', '~/dir').should == "#{@var_home}/foo" + end + end + + it "accepts objects that have a #to_path method" do + File.expand_path(mock_to_path("a"), mock_to_path("#{@tmpdir}")) + end + + it "raises a TypeError if not passed a String type" do + -> { File.expand_path(1) }.should raise_error(TypeError) + -> { File.expand_path(nil) }.should raise_error(TypeError) + -> { File.expand_path(true) }.should raise_error(TypeError) + end + + platform_is_not :windows do + it "expands /./dir to /dir" do + File.expand_path("/./dir").should == "/dir" + end + end + + platform_is :windows do + it "expands C:/./dir to C:/dir" do + File.expand_path("C:/./dir").should == "C:/dir" + end + end + + it "returns a String in the same encoding as the argument" do + Encoding.default_external = Encoding::SHIFT_JIS + + path = "./a".dup.force_encoding Encoding::CP1251 + File.expand_path(path).encoding.should equal(Encoding::CP1251) + + weird_path = [222, 173, 190, 175].pack('C*') + File.expand_path(weird_path).encoding.should equal(Encoding::BINARY) + end + + platform_is_not :windows do + it "expands a path when the default external encoding is BINARY" do + Encoding.default_external = Encoding::BINARY + path_8bit = [222, 173, 190, 175].pack('C*') + File.expand_path( path_8bit, @rootdir).should == "#{@rootdir}" + path_8bit + end + end + + it "expands a path with multi-byte characters" do + File.expand_path("Ångström").should == "#{@base}/Ångström" + end + + platform_is_not :windows do + it "raises an Encoding::CompatibilityError if the external encoding is not compatible" do + Encoding.default_external = Encoding::UTF_16BE + -> { File.expand_path("./a") }.should raise_error(Encoding::CompatibilityError) + end + end + + it "does not modify the string argument" do + str = "./a/b/../c" + File.expand_path(str, @base).should == "#{@base}/a/c" + str.should == "./a/b/../c" + end + + it "does not modify a HOME string argument" do + str = "~/a" + File.expand_path(str).should == "#{Dir.home}/a" + str.should == "~/a" + end + + it "returns a String when passed a String subclass" do + str = FileSpecs::SubString.new "./a/b/../c" + path = File.expand_path(str, @base) + path.should == "#{@base}/a/c" + path.should be_an_instance_of(String) + end +end + +platform_is_not :windows do + describe "File.expand_path when HOME is set" do + before :each do + @home = ENV["HOME"] + ENV["HOME"] = "/rubyspec_home" + end + + after :each do + ENV["HOME"] = @home + end + + it "converts a pathname to an absolute pathname, using ~ (home) as base" do + home = "/rubyspec_home" + File.expand_path('~').should == home + File.expand_path('~', '/tmp/gumby/ddd').should == home + File.expand_path('~/a', '/tmp/gumby/ddd').should == File.join(home, 'a') + end + + it "does not return a frozen string" do + home = "/rubyspec_home" + File.expand_path('~').should_not.frozen? + File.expand_path('~', '/tmp/gumby/ddd').should_not.frozen? + File.expand_path('~/a', '/tmp/gumby/ddd').should_not.frozen? + end + end + + + describe "File.expand_path when HOME is not set" do + before :each do + @home = ENV["HOME"] + end + + after :each do + ENV["HOME"] = @home + end + + guard -> { + # We need to check if getlogin(3) returns non-NULL, + # as MRI only checks getlogin(3) for expanding '~' if $HOME is not set. + user = ENV.delete("USER") + begin + Etc.getlogin != nil + rescue + false + ensure + ENV["USER"] = user + end + } do + it "uses the user database when passed '~' if HOME is nil" do + ENV.delete "HOME" + File.directory?(File.expand_path("~")).should == true + end + + it "uses the user database when passed '~/' if HOME is nil" do + ENV.delete "HOME" + File.directory?(File.expand_path("~/")).should == true + end + end + + it "raises an ArgumentError when passed '~' if HOME == ''" do + ENV["HOME"] = "" + -> { File.expand_path("~") }.should raise_error(ArgumentError) + end + end + + describe "File.expand_path with a non-absolute HOME" do + before :each do + @home = ENV["HOME"] + end + + after :each do + ENV["HOME"] = @home + end + + it "raises an ArgumentError" do + ENV["HOME"] = "non-absolute" + -> { File.expand_path("~") }.should raise_error(ArgumentError, 'non-absolute home') + end + end +end diff --git a/spec/ruby/core/file/extname_spec.rb b/spec/ruby/core/file/extname_spec.rb new file mode 100644 index 0000000000..d20cf813d9 --- /dev/null +++ b/spec/ruby/core/file/extname_spec.rb @@ -0,0 +1,76 @@ +# -*- encoding: utf-8 -*- +require_relative '../../spec_helper' + +describe "File.extname" do + it "returns the extension (the portion of file name in path after the period)" do + File.extname("foo.rb").should == ".rb" + File.extname("/foo/bar.rb").should == ".rb" + File.extname("/foo.rb/bar.c").should == ".c" + File.extname("bar").should == "" + File.extname(".bashrc").should == "" + File.extname("/foo.bar/baz").should == "" + File.extname(".app.conf").should == ".conf" + end + + it "returns unfrozen strings" do + File.extname("foo.rb").frozen?.should == false + File.extname("/foo/bar.rb").frozen?.should == false + File.extname("/foo.rb/bar.c").frozen?.should == false + File.extname("bar").frozen?.should == false + File.extname(".bashrc").frozen?.should == false + File.extname("/foo.bar/baz").frozen?.should == false + File.extname(".app.conf").frozen?.should == false + end + + it "returns the extension for edge cases" do + File.extname("").should == "" + File.extname(".").should == "" + File.extname("/").should == "" + File.extname("/.").should == "" + File.extname("..").should == "" + File.extname("...").should == "" + File.extname("....").should == "" + end + + describe "for a filename ending with a dot" do + platform_is :windows do + it "returns ''" do + File.extname(".foo.").should == "" + File.extname("foo.").should == "" + end + end + + platform_is_not :windows do + it "returns '.'" do + File.extname(".foo.").should == "." + File.extname("foo.").should == "." + end + end + end + + it "returns only the last extension of a file with several dots" do + File.extname("a.b.c.d.e").should == ".e" + end + + it "accepts an object that has a #to_path method" do + File.extname(mock_to_path("a.b.c.d.e")).should == ".e" + end + + it "raises a TypeError if not passed a String type" do + -> { File.extname(nil) }.should raise_error(TypeError) + -> { File.extname(0) }.should raise_error(TypeError) + -> { File.extname(true) }.should raise_error(TypeError) + -> { File.extname(false) }.should raise_error(TypeError) + end + + it "raises an ArgumentError if not passed one argument" do + -> { File.extname }.should raise_error(ArgumentError) + -> { File.extname("foo.bar", "foo.baz") }.should raise_error(ArgumentError) + end + + + it "returns the extension for a multibyte filename" do + File.extname('Имя.m4a').should == ".m4a" + end + +end diff --git a/spec/ruby/core/file/file_spec.rb b/spec/ruby/core/file/file_spec.rb new file mode 100644 index 0000000000..8a9dfd5fe2 --- /dev/null +++ b/spec/ruby/core/file/file_spec.rb @@ -0,0 +1,16 @@ +require_relative '../../spec_helper' +require_relative '../../shared/file/file' + +describe "File" do + it "includes Enumerable" do + File.include?(Enumerable).should == true + end + + it "includes File::Constants" do + File.include?(File::Constants).should == true + end +end + +describe "File.file?" do + it_behaves_like :file_file, :file?, File +end diff --git a/spec/ruby/core/file/fixtures/common.rb b/spec/ruby/core/file/fixtures/common.rb new file mode 100644 index 0000000000..50721388ad --- /dev/null +++ b/spec/ruby/core/file/fixtures/common.rb @@ -0,0 +1,22 @@ +module FileSpecs + class SubString < String; end + + def self.make_closer(obj, exc=nil) + ScratchPad << :file_opened + + class << obj + attr_accessor :close_exception + + alias_method :original_close, :close + + def close + original_close + ScratchPad << :file_closed + + raise @close_exception if @close_exception + end + end + + obj.close_exception = exc + end +end diff --git a/spec/ruby/core/file/fixtures/do_not_remove b/spec/ruby/core/file/fixtures/do_not_remove new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/spec/ruby/core/file/fixtures/do_not_remove @@ -0,0 +1 @@ + diff --git a/spec/ruby/core/file/fixtures/file_types.rb b/spec/ruby/core/file/fixtures/file_types.rb new file mode 100644 index 0000000000..109bcfe42e --- /dev/null +++ b/spec/ruby/core/file/fixtures/file_types.rb @@ -0,0 +1,66 @@ +module FileSpecs + def self.configure_types + return if @configured + + @file = tmp("test.txt") + @dir = Dir.pwd + @fifo = tmp("test_fifo") + @link = tmp("test_link") + + platform_is_not :windows do + @block = `find /dev /devices -type b 2>/dev/null`.split("\n").first + @char = `{ tty || find /dev /devices -type c; } 2>/dev/null`.split("\n").last + end + + @configured = true + end + + def self.normal_file + touch(@file) + yield @file + ensure + rm_r @file + end + + def self.directory + yield @dir + end + + def self.fifo + File.mkfifo(@fifo) + yield @fifo + ensure + rm_r @fifo + end + + def self.block_device + raise "Could not find a block device" unless @block + yield @block + end + + def self.character_device + raise "Could not find a character device" unless @char + yield @char + end + + def self.symlink + touch(@file) + File.symlink(@file, @link) + yield @link + ensure + rm_r @file, @link + end + + def self.socket + require_relative '../../../library/socket/fixtures/classes.rb' + + name = SocketSpecs.socket_path + socket = UNIXServer.new name + begin + yield name + ensure + socket.close + rm_r name + end + end +end diff --git a/spec/ruby/core/file/flock_spec.rb b/spec/ruby/core/file/flock_spec.rb new file mode 100644 index 0000000000..23ddf89ed8 --- /dev/null +++ b/spec/ruby/core/file/flock_spec.rb @@ -0,0 +1,74 @@ +require_relative '../../spec_helper' + +describe "File#flock" do + before :each do + ScratchPad.record [] + + @name = tmp("flock_test") + touch(@name) + + @file = File.open @name, "w+" + end + + after :each do + @file.flock File::LOCK_UN + @file.close + + rm_r @name + end + + it "exclusively locks a file" do + @file.flock(File::LOCK_EX).should == 0 + @file.flock(File::LOCK_UN).should == 0 + end + + it "non-exclusively locks a file" do + @file.flock(File::LOCK_SH).should == 0 + @file.flock(File::LOCK_UN).should == 0 + end + + it "returns false if trying to lock an exclusively locked file" do + @file.flock File::LOCK_EX + + ruby_exe(<<-END_OF_CODE).should == "false" + File.open('#{@name}', "w") do |f2| + print f2.flock(File::LOCK_EX | File::LOCK_NB).to_s + end + END_OF_CODE + end + + it "blocks if trying to lock an exclusively locked file" do + @file.flock File::LOCK_EX + + out = ruby_exe(<<-END_OF_CODE) + running = false + + t = Thread.new do + File.open('#{@name}', "w") do |f2| + puts "before" + running = true + f2.flock(File::LOCK_EX) + puts "after" + end + end + + Thread.pass until running + Thread.pass while t.status and t.status != "sleep" + sleep 0.1 + + t.kill + t.join + END_OF_CODE + + out.should == "before\n" + end + + it "returns 0 if trying to lock a non-exclusively locked file" do + @file.flock File::LOCK_SH + + File.open(@name, "r") do |f2| + f2.flock(File::LOCK_SH | File::LOCK_NB).should == 0 + f2.flock(File::LOCK_UN).should == 0 + end + end +end diff --git a/spec/ruby/core/file/fnmatch_spec.rb b/spec/ruby/core/file/fnmatch_spec.rb new file mode 100644 index 0000000000..a1b7fa12b3 --- /dev/null +++ b/spec/ruby/core/file/fnmatch_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' +require_relative 'shared/fnmatch' + +describe "File.fnmatch" do + it_behaves_like :file_fnmatch, :fnmatch +end + +describe "File.fnmatch?" do + it_behaves_like :file_fnmatch, :fnmatch? +end diff --git a/spec/ruby/core/file/ftype_spec.rb b/spec/ruby/core/file/ftype_spec.rb new file mode 100644 index 0000000000..cdddc404dc --- /dev/null +++ b/spec/ruby/core/file/ftype_spec.rb @@ -0,0 +1,82 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/file_types' + +describe "File.ftype" do + before :all do + FileSpecs.configure_types + end + + it "raises ArgumentError if not given exactly one filename" do + -> { File.ftype }.should raise_error(ArgumentError) + -> { File.ftype('blah', 'bleh') }.should raise_error(ArgumentError) + end + + it "raises Errno::ENOENT if the file is not valid" do + -> { + File.ftype("/#{$$}#{Time.now.to_f}") + }.should raise_error(Errno::ENOENT) + end + + it "returns a String" do + FileSpecs.normal_file do |file| + File.ftype(file).should be_kind_of(String) + end + end + + it "returns 'file' when the file is a file" do + FileSpecs.normal_file do |file| + File.ftype(file).should == 'file' + end + end + + it "returns 'directory' when the file is a dir" do + FileSpecs.directory do |dir| + File.ftype(dir).should == 'directory' + end + end + + it "uses to_path to convert arguments" do + FileSpecs.normal_file do |file| + obj = mock('path') + obj.should_receive(:to_path).and_return(file) + File.ftype(obj).should == 'file' + end + end + + # Both FreeBSD and Windows does not have block devices + platform_is_not :freebsd, :windows do + with_block_device do + it "returns 'blockSpecial' when the file is a block" do + FileSpecs.block_device do |block| + File.ftype(block).should == 'blockSpecial' + end + end + end + end + + platform_is_not :windows do + it "returns 'characterSpecial' when the file is a char" do + FileSpecs.character_device do |char| + File.ftype(char).should == 'characterSpecial' + end + end + + it "returns 'link' when the file is a link" do + FileSpecs.symlink do |link| + File.ftype(link).should == 'link' + end + end + + it "returns fifo when the file is a fifo" do + FileSpecs.fifo do |fifo| + File.ftype(fifo).should == 'fifo' + end + end + + it "returns 'socket' when the file is a socket" do + FileSpecs.socket do |socket| + File.ftype(socket).should == 'socket' + end + end + end +end diff --git a/spec/ruby/core/file/grpowned_spec.rb b/spec/ruby/core/file/grpowned_spec.rb new file mode 100644 index 0000000000..8ddac5237c --- /dev/null +++ b/spec/ruby/core/file/grpowned_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' +require_relative '../../shared/file/grpowned' + +describe "File.grpowned?" do + it_behaves_like :file_grpowned, :grpowned?, File + + it "returns false if file the does not exist" do + File.grpowned?("i_am_a_bogus_file").should == false + end +end diff --git a/spec/ruby/core/file/identical_spec.rb b/spec/ruby/core/file/identical_spec.rb new file mode 100644 index 0000000000..bbeaef24d2 --- /dev/null +++ b/spec/ruby/core/file/identical_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative '../../shared/file/identical' + +describe "File.identical?" do + it_behaves_like :file_identical, :identical?, File +end diff --git a/spec/ruby/core/file/initialize_spec.rb b/spec/ruby/core/file/initialize_spec.rb new file mode 100644 index 0000000000..9a76a95260 --- /dev/null +++ b/spec/ruby/core/file/initialize_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../spec_helper' + +describe "File#initialize" do + after :each do + @io.close if @io + end + + it "accepts encoding options in mode parameter" do + @io = File.new(__FILE__, 'r:UTF-8:iso-8859-1') + @io.external_encoding.to_s.should == 'UTF-8' + @io.internal_encoding.to_s.should == 'ISO-8859-1' + end + + it "accepts encoding options as a hash parameter" do + @io = File.new(__FILE__, 'r', encoding: 'UTF-8:iso-8859-1') + @io.external_encoding.to_s.should == 'UTF-8' + @io.internal_encoding.to_s.should == 'ISO-8859-1' + end +end diff --git a/spec/ruby/core/file/inspect_spec.rb b/spec/ruby/core/file/inspect_spec.rb new file mode 100644 index 0000000000..148e789c62 --- /dev/null +++ b/spec/ruby/core/file/inspect_spec.rb @@ -0,0 +1,17 @@ +require_relative '../../spec_helper' + +describe "File#inspect" do + before :each do + @name = tmp("file_inspect.txt") + @file = File.open @name, "w" + end + + after :each do + @file.close unless @file.closed? + rm_r @name + end + + it "returns a String" do + @file.inspect.should be_an_instance_of(String) + end +end diff --git a/spec/ruby/core/file/join_spec.rb b/spec/ruby/core/file/join_spec.rb new file mode 100644 index 0000000000..0feedbae93 --- /dev/null +++ b/spec/ruby/core/file/join_spec.rb @@ -0,0 +1,148 @@ +require_relative '../../spec_helper' + +describe "File.join" do + # see [ruby-core:46804] for the 4 following rules + it "changes only boundaries separators" do + File.join("file/\\/usr/", "/bin").should == "file/\\/usr/bin" + File.join("file://usr", "bin").should == "file://usr/bin" + end + + it "respects the given separator if only one part has a boundary separator" do + File.join("usr/", "bin").should == "usr/bin" + File.join("usr", "/bin").should == "usr/bin" + File.join("usr//", "bin").should == "usr//bin" + File.join("usr", "//bin").should == "usr//bin" + end + + it "joins parts using File::SEPARATOR if there are no boundary separators" do + File.join("usr", "bin").should == "usr/bin" + end + + it "prefers the separator of the right part if both parts have separators" do + File.join("usr/", "//bin").should == "usr//bin" + File.join("usr//", "/bin").should == "usr/bin" + end + + platform_is :windows do + it "respects given separator if only one part has a boundary separator" do + File.join("C:\\", 'windows').should == "C:\\windows" + File.join("C:", "\\windows").should == "C:\\windows" + File.join("\\\\", "usr").should == "\\\\usr" + end + + it "prefers the separator of the right part if both parts have separators" do + File.join("C:/", "\\windows").should == "C:\\windows" + File.join("C:\\", "/windows").should == "C:/windows" + end + end + + platform_is_not :windows do + it "does not treat \\ as a separator on non-Windows" do + File.join("usr\\", 'bin').should == "usr\\/bin" + File.join("usr", "\\bin").should == "usr/\\bin" + File.join("usr/", "\\bin").should == "usr/\\bin" + File.join("usr\\", "/bin").should == "usr\\/bin" + end + end + + it "returns an empty string when given no arguments" do + File.join.should == "" + end + + it "returns a duplicate string when given a single argument" do + str = "usr" + File.join(str).should == str + File.join(str).should_not equal(str) + end + + it "supports any number of arguments" do + File.join("a", "b", "c", "d").should == "a/b/c/d" + end + + it "flattens nested arrays" do + File.join(["a", "b", "c"]).should == "a/b/c" + File.join(["a", ["b", ["c"]]]).should == "a/b/c" + end + + it "inserts the separator in between empty strings and arrays" do + File.join("").should == "" + File.join("", "").should == "/" + File.join(["", ""]).should == "/" + File.join("a", "").should == "a/" + File.join("", "a").should == "/a" + + File.join([]).should == "" + File.join([], []).should == "/" + File.join([[], []]).should == "/" + File.join("a", []).should == "a/" + File.join([], "a").should == "/a" + end + + it "handles leading parts edge cases" do + File.join("/bin") .should == "/bin" + File.join("", "bin") .should == "/bin" + File.join("/", "bin") .should == "/bin" + File.join("/", "/bin").should == "/bin" + end + + it "handles trailing parts edge cases" do + File.join("bin", "") .should == "bin/" + File.join("bin/") .should == "bin/" + File.join("bin/", "") .should == "bin/" + File.join("bin", "/") .should == "bin/" + File.join("bin/", "/").should == "bin/" + end + + it "handles middle parts edge cases" do + File.join("usr", "", "bin") .should == "usr/bin" + File.join("usr/", "", "bin") .should == "usr/bin" + File.join("usr", "", "/bin").should == "usr/bin" + File.join("usr/", "", "/bin").should == "usr/bin" + end + + # TODO: See MRI svn r23306. Add patchlevel when there is a release. + it "raises an ArgumentError if passed a recursive array" do + a = ["a"] + a << a + -> { File.join a }.should raise_error(ArgumentError) + end + + it "raises a TypeError exception when args are nil" do + -> { File.join nil }.should raise_error(TypeError) + end + + it "calls #to_str" do + -> { File.join(mock('x')) }.should raise_error(TypeError) + + bin = mock("bin") + bin.should_receive(:to_str).exactly(:twice).and_return("bin") + File.join(bin).should == "bin" + File.join("usr", bin).should == "usr/bin" + end + + it "doesn't mutate the object when calling #to_str" do + usr = mock("usr") + str = "usr" + usr.should_receive(:to_str).and_return(str) + File.join(usr, "bin").should == "usr/bin" + str.should == "usr" + end + + it "calls #to_path" do + -> { File.join(mock('x')) }.should raise_error(TypeError) + + bin = mock("bin") + bin.should_receive(:to_path).exactly(:twice).and_return("bin") + File.join(bin).should == "bin" + File.join("usr", bin).should == "usr/bin" + end + + it "raises errors for null bytes" do + -> { File.join("\x00x", "metadata.gz") }.should raise_error(ArgumentError) { |e| + e.message.should == 'string contains null byte' + } + -> { File.join("metadata.gz", "\x00x") }.should raise_error(ArgumentError) { |e| + e.message.should == 'string contains null byte' + } + end +end diff --git a/spec/ruby/core/file/lchmod_spec.rb b/spec/ruby/core/file/lchmod_spec.rb new file mode 100644 index 0000000000..3c44374983 --- /dev/null +++ b/spec/ruby/core/file/lchmod_spec.rb @@ -0,0 +1,32 @@ +require_relative '../../spec_helper' + +describe "File.lchmod" do + platform_is_not :linux, :windows, :openbsd, :aix do + before :each do + @fname = tmp('file_chmod_test') + @lname = @fname + '.lnk' + + touch(@fname) { |f| f.write "rubinius" } + + rm_r @lname + File.symlink @fname, @lname + end + + after :each do + rm_r @lname, @fname + end + + it "changes the file mode of the link and not of the file" do + File.chmod(0222, @lname).should == 1 + File.lchmod(0755, @lname).should == 1 + + File.lstat(@lname).should.executable? + File.lstat(@lname).should.readable? + File.lstat(@lname).should.writable? + + File.stat(@lname).should_not.executable? + File.stat(@lname).should_not.readable? + File.stat(@lname).should.writable? + end + end +end diff --git a/spec/ruby/core/file/lchown_spec.rb b/spec/ruby/core/file/lchown_spec.rb new file mode 100644 index 0000000000..8d95d287ba --- /dev/null +++ b/spec/ruby/core/file/lchown_spec.rb @@ -0,0 +1,59 @@ +require_relative '../../spec_helper' + +as_superuser do + describe "File.lchown" do + platform_is_not :windows do + before :each do + @fname = tmp('file_chown_test') + @lname = @fname + '.lnk' + + touch(@fname) { |f| f.chown 501, 501 } + + rm_r @lname + File.symlink @fname, @lname + end + + after :each do + rm_r @lname, @fname + end + + it "changes the owner id of the file" do + File.lchown 502, nil, @lname + File.stat(@fname).uid.should == 501 + File.lstat(@lname).uid.should == 502 + File.lchown 0, nil, @lname + File.stat(@fname).uid.should == 501 + File.lstat(@lname).uid.should == 0 + end + + it "changes the group id of the file" do + File.lchown nil, 502, @lname + File.stat(@fname).gid.should == 501 + File.lstat(@lname).gid.should == 502 + File.lchown nil, 0, @lname + File.stat(@fname).uid.should == 501 + File.lstat(@lname).uid.should == 0 + end + + it "does not modify the owner id of the file if passed nil or -1" do + File.lchown 502, nil, @lname + File.lchown nil, nil, @lname + File.lstat(@lname).uid.should == 502 + File.lchown nil, -1, @lname + File.lstat(@lname).uid.should == 502 + end + + it "does not modify the group id of the file if passed nil or -1" do + File.lchown nil, 502, @lname + File.lchown nil, nil, @lname + File.lstat(@lname).gid.should == 502 + File.lchown nil, -1, @lname + File.lstat(@lname).gid.should == 502 + end + + it "returns the number of files processed" do + File.lchown(nil, nil, @lname, @lname).should == 2 + end + end + end +end diff --git a/spec/ruby/core/file/link_spec.rb b/spec/ruby/core/file/link_spec.rb new file mode 100644 index 0000000000..a5d5b4815f --- /dev/null +++ b/spec/ruby/core/file/link_spec.rb @@ -0,0 +1,39 @@ +require_relative '../../spec_helper' + +describe "File.link" do + before :each do + @file = tmp("file_link.txt") + @link = tmp("file_link.lnk") + + rm_r @link + touch @file + end + + after :each do + rm_r @link, @file + end + + platform_is_not :windows, :android do + it "link a file with another" do + File.link(@file, @link).should == 0 + File.should.exist?(@link) + File.identical?(@file, @link).should == true + end + + it "raises an Errno::EEXIST if the target already exists" do + File.link(@file, @link) + -> { File.link(@file, @link) }.should raise_error(Errno::EEXIST) + end + + it "raises an ArgumentError if not passed two arguments" do + -> { File.link }.should raise_error(ArgumentError) + -> { File.link(@file) }.should raise_error(ArgumentError) + -> { File.link(@file, @link, @file) }.should raise_error(ArgumentError) + end + + it "raises a TypeError if not passed String types" do + -> { File.link(@file, nil) }.should raise_error(TypeError) + -> { File.link(@file, 1) }.should raise_error(TypeError) + end + end +end diff --git a/spec/ruby/core/file/lstat_spec.rb b/spec/ruby/core/file/lstat_spec.rb new file mode 100644 index 0000000000..a5ea9d15a5 --- /dev/null +++ b/spec/ruby/core/file/lstat_spec.rb @@ -0,0 +1,33 @@ +require_relative '../../spec_helper' +require_relative 'shared/stat' + +describe "File.lstat" do + it_behaves_like :file_stat, :lstat +end + +describe "File.lstat" do + + before :each do + @file = tmp('i_exist') + @link = tmp('i_am_a_symlink') + touch(@file) { |f| f.write 'rubinius' } + File.symlink(@file, @link) + end + + after :each do + rm_r @link, @file + end + + platform_is_not :windows do + it "returns a File::Stat object with symlink properties for a symlink" do + st = File.lstat(@link) + + st.should.symlink? + st.should_not.file? + end + end +end + +describe "File#lstat" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/file/lutime_spec.rb b/spec/ruby/core/file/lutime_spec.rb new file mode 100644 index 0000000000..0f6df42ea3 --- /dev/null +++ b/spec/ruby/core/file/lutime_spec.rb @@ -0,0 +1,43 @@ +require_relative '../../spec_helper' +require_relative 'shared/update_time' + +platform_is_not :windows do + describe "File.lutime" do + it_behaves_like :update_time, :lutime + end + + describe "File.lutime" do + before :each do + @atime = Time.utc(2000) + @mtime = Time.utc(2001) + @file = tmp("specs_lutime_file") + @symlink = tmp("specs_lutime_symlink") + touch @file + File.symlink(@file, @symlink) + end + + after :each do + rm_r @file, @symlink + end + + it "sets the access and modification time for a regular file" do + File.lutime(@atime, @mtime, @file) + stat = File.stat(@file) + stat.atime.should == @atime + stat.mtime.should === @mtime + end + + it "sets the access and modification time for a symlink" do + original = File.stat(@file) + + File.lutime(@atime, @mtime, @symlink) + stat = File.lstat(@symlink) + stat.atime.should == @atime + stat.mtime.should === @mtime + + file = File.stat(@file) + file.atime.should == original.atime + file.mtime.should == original.mtime + end + end +end diff --git a/spec/ruby/core/file/mkfifo_spec.rb b/spec/ruby/core/file/mkfifo_spec.rb new file mode 100644 index 0000000000..19298c967c --- /dev/null +++ b/spec/ruby/core/file/mkfifo_spec.rb @@ -0,0 +1,51 @@ +require_relative '../../spec_helper' + +describe "File.mkfifo" do + platform_is_not :windows do + before do + @path = tmp('fifo') + end + + after do + rm_r(@path) + end + + context "when path passed responds to :to_path" do + it "creates a FIFO file at the path specified" do + File.mkfifo(@path) + File.ftype(@path).should == "fifo" + end + end + + context "when path passed is not a String value" do + it "raises a TypeError" do + -> { File.mkfifo(:"/tmp/fifo") }.should raise_error(TypeError) + end + end + + context "when path does not exist" do + it "raises an Errno::ENOENT exception" do + -> { File.mkfifo("/bogus/path") }.should raise_error(Errno::ENOENT) + end + end + + it "creates a FIFO file at the passed path" do + File.mkfifo(@path.to_s) + File.ftype(@path).should == "fifo" + end + + it "creates a FIFO file with passed mode & ~umask" do + File.mkfifo(@path, 0755) + File.stat(@path).mode.should == 010755 & ~File.umask + end + + it "creates a FIFO file with a default mode of 0666 & ~umask" do + File.mkfifo(@path) + File.stat(@path).mode.should == 010666 & ~File.umask + end + + it "returns 0 after creating the FIFO file" do + File.mkfifo(@path).should == 0 + end + end +end diff --git a/spec/ruby/core/file/mtime_spec.rb b/spec/ruby/core/file/mtime_spec.rb new file mode 100644 index 0000000000..0e9c95caee --- /dev/null +++ b/spec/ruby/core/file/mtime_spec.rb @@ -0,0 +1,56 @@ +require_relative '../../spec_helper' + +describe "File.mtime" do + before :each do + @filename = tmp('i_exist') + touch(@filename) { @mtime = Time.now } + end + + after :each do + rm_r @filename + end + + it "returns the modification Time of the file" do + File.mtime(@filename).should be_kind_of(Time) + File.mtime(@filename).should be_close(@mtime, TIME_TOLERANCE) + end + + platform_is :linux, :windows do + unless ENV.key?('TRAVIS') # https://bugs.ruby-lang.org/issues/17926 + it "returns the modification Time of the file with microseconds" do + supports_subseconds = Integer(`stat -c%y '#{__FILE__}'`[/\.(\d{1,6})/, 1], 10) + if supports_subseconds != 0 + expected_time = Time.at(Time.now.to_i + 0.123456) + File.utime 0, expected_time, @filename + File.mtime(@filename).usec.should == expected_time.usec + else + File.mtime(__FILE__).usec.should == 0 + end + rescue Errno::ENOENT => e + # Windows don't have stat command. + skip e.message + end + end + end + + it "raises an Errno::ENOENT exception if the file is not found" do + -> { File.mtime('bogus') }.should raise_error(Errno::ENOENT) + end +end + +describe "File#mtime" do + before :each do + @filename = tmp('i_exist') + @f = File.open(@filename, 'w') + end + + after :each do + @f.close + rm_r @filename + end + + it "returns the modification Time of the file" do + @f.mtime.should be_kind_of(Time) + end + +end diff --git a/spec/ruby/core/file/new_spec.rb b/spec/ruby/core/file/new_spec.rb new file mode 100644 index 0000000000..1e82a070b1 --- /dev/null +++ b/spec/ruby/core/file/new_spec.rb @@ -0,0 +1,223 @@ +require_relative '../../spec_helper' +require_relative 'shared/open' + +describe "File.new" do + before :each do + @file = tmp('test.txt') + @fh = nil + @flags = File::CREAT | File::TRUNC | File::WRONLY + touch @file + end + + after :each do + @fh.close if @fh + rm_r @file + end + + it "returns a new File with mode string" do + @fh = File.new(@file, 'w') + @fh.should be_kind_of(File) + File.should.exist?(@file) + end + + it "returns a new File with mode num" do + @fh = File.new(@file, @flags) + @fh.should be_kind_of(File) + File.should.exist?(@file) + end + + it "returns a new File with modus num and permissions" do + rm_r @file + File.umask(0011) + @fh = File.new(@file, @flags, 0755) + @fh.should be_kind_of(File) + platform_is_not :windows do + File.stat(@file).mode.to_s(8).should == "100744" + end + File.should.exist?(@file) + end + + it "creates the file and returns writable descriptor when called with 'w' mode and r-o permissions" do + # it should be possible to write to such a file via returned descriptor, + # even though the file permissions are r-r-r. + + rm_r @file + begin + f = File.new(@file, "w", 0444) + -> { f.puts("test") }.should_not raise_error(IOError) + ensure + f.close + end + File.should.exist?(@file) + File.read(@file).should == "test\n" + end + + platform_is_not :windows do + it "opens the existing file, does not change permissions even when they are specified" do + File.chmod(0644, @file) # r-w perms + orig_perms = File.stat(@file).mode & 0777 + begin + f = File.new(@file, "w", 0444) # r-o perms, but they should be ignored + f.puts("test") + ensure + f.close + end + perms = File.stat(@file).mode & 0777 + perms.should == orig_perms + + # it should be still possible to read from the file + File.read(@file).should == "test\n" + end + end + + it "returns a new File with modus fd" do + @fh = File.new(@file) + fh_copy = File.new(@fh.fileno) + fh_copy.autoclose = false + fh_copy.should be_kind_of(File) + File.should.exist?(@file) + end + + it "returns a new read-only File when mode is not specified" do + @fh = File.new(@file) + + -> { @fh.puts("test") }.should raise_error(IOError) + @fh.read.should == "" + File.should.exist?(@file) + end + + it "returns a new read-only File when mode is not specified but flags option is present" do + @fh = File.new(@file, flags: File::CREAT) + + -> { @fh.puts("test") }.should raise_error(IOError) + @fh.read.should == "" + File.should.exist?(@file) + end + + it "creates a new file when use File::EXCL mode" do + @fh = File.new(@file, File::EXCL) + @fh.should be_kind_of(File) + File.should.exist?(@file) + end + + it "raises an Errno::EEXIST if the file exists when create a new file with File::CREAT|File::EXCL" do + -> { @fh = File.new(@file, File::CREAT|File::EXCL) }.should raise_error(Errno::EEXIST) + end + + it "creates a new file when use File::WRONLY|File::APPEND mode" do + @fh = File.new(@file, File::WRONLY|File::APPEND) + @fh.should be_kind_of(File) + File.should.exist?(@file) + end + + it "returns a new File when use File::APPEND mode" do + @fh = File.new(@file, File::APPEND) + @fh.should be_kind_of(File) + File.should.exist?(@file) + end + + it "returns a new File when use File::RDONLY|File::APPEND mode" do + @fh = File.new(@file, File::RDONLY|File::APPEND) + @fh.should be_kind_of(File) + File.should.exist?(@file) + end + + it "returns a new File when use File::RDONLY|File::WRONLY mode" do + @fh = File.new(@file, File::RDONLY|File::WRONLY) + @fh.should be_kind_of(File) + File.should.exist?(@file) + end + + it "creates a new file when use File::WRONLY|File::TRUNC mode" do + @fh = File.new(@file, File::WRONLY|File::TRUNC) + @fh.should be_kind_of(File) + File.should.exist?(@file) + end + + it "returns a new read-only File when use File::RDONLY|File::CREAT mode" do + @fh = File.new(@file, File::RDONLY|File::CREAT) + @fh.should be_kind_of(File) + File.should.exist?(@file) + + # it's read-only + -> { @fh.puts("test") }.should raise_error(IOError) + @fh.read.should == "" + end + + it "returns a new read-only File when use File::CREAT mode" do + @fh = File.new(@file, File::CREAT) + @fh.should be_kind_of(File) + File.should.exist?(@file) + + # it's read-only + -> { @fh.puts("test") }.should raise_error(IOError) + @fh.read.should == "" + end + + it "coerces filename using to_str" do + name = mock("file") + name.should_receive(:to_str).and_return(@file) + @fh = File.new(name, "w") + File.should.exist?(@file) + end + + it "coerces filename using #to_path" do + name = mock("file") + name.should_receive(:to_path).and_return(@file) + @fh = File.new(name, "w") + File.should.exist?(@file) + end + + it "accepts options as a keyword argument" do + @fh = File.new(@file, 'w', 0755, flags: @flags) + @fh.should be_kind_of(File) + @fh.close + + -> { + @fh = File.new(@file, 'w', 0755, {flags: @flags}) + }.should raise_error(ArgumentError, "wrong number of arguments (given 4, expected 1..3)") + end + + it "bitwise-ORs mode and flags option" do + -> { + @fh = File.new(@file, 'w', flags: File::EXCL) + }.should raise_error(Errno::EEXIST, /File exists/) + + -> { + @fh = File.new(@file, mode: 'w', flags: File::EXCL) + }.should raise_error(Errno::EEXIST, /File exists/) + end + + it "does not use the given block and warns to use File::open" do + -> { + @fh = File.new(@file) { raise } + }.should complain(/warning: File::new\(\) does not take block; use File::open\(\) instead/) + end + + it "raises a TypeError if the first parameter can't be coerced to a string" do + -> { File.new(true) }.should raise_error(TypeError) + -> { File.new(false) }.should raise_error(TypeError) + end + + it "raises a TypeError if the first parameter is nil" do + -> { File.new(nil) }.should raise_error(TypeError) + end + + it "raises an Errno::EBADF if the first parameter is an invalid file descriptor" do + -> { File.new(-1) }.should raise_error(Errno::EBADF) + end + + platform_is_not :windows do + it "can't alter mode or permissions when opening a file" do + @fh = File.new(@file) + -> { + f = File.new(@fh.fileno, @flags) + f.autoclose = false + }.should raise_error(Errno::EINVAL) + end + end + + platform_is_not :windows do + it_behaves_like :open_directory, :new + end +end diff --git a/spec/ruby/core/file/null_spec.rb b/spec/ruby/core/file/null_spec.rb new file mode 100644 index 0000000000..355b72b799 --- /dev/null +++ b/spec/ruby/core/file/null_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../spec_helper' + +describe "File::NULL" do + platform_is :windows do + it "returns NUL as a string" do + File::NULL.should == 'NUL' + end + end + + platform_is_not :windows do + it "returns /dev/null as a string" do + File::NULL.should == '/dev/null' + end + end +end diff --git a/spec/ruby/core/file/open_spec.rb b/spec/ruby/core/file/open_spec.rb new file mode 100644 index 0000000000..6bfc16bbf9 --- /dev/null +++ b/spec/ruby/core/file/open_spec.rb @@ -0,0 +1,713 @@ +# encoding: utf-8 + +require_relative '../../spec_helper' +require_relative 'fixtures/common' +require_relative 'shared/open' + +describe "File.open" do + before :all do + @file = tmp("file_open.txt") + @unicode_path = tmp("こんにちは.txt") + @nonexistent = tmp("fake.txt") + rm_r @file, @nonexistent + end + + before :each do + ScratchPad.record [] + + @fh = @fd = nil + @flags = File::CREAT | File::TRUNC | File::WRONLY + touch @file + end + + after :each do + @fh.close if @fh and not @fh.closed? + rm_r @file, @unicode_path, @nonexistent + end + + describe "with a block" do + it "does not raise error when file is closed inside the block" do + @fh = File.open(@file) { |fh| fh.close; fh } + @fh.should.closed? + end + + it "invokes close on an opened file when exiting the block" do + File.open(@file, 'r') { |f| FileSpecs.make_closer f } + + ScratchPad.recorded.should == [:file_opened, :file_closed] + end + + it "propagates non-StandardErrors produced by close" do + -> { + File.open(@file, 'r') { |f| FileSpecs.make_closer f, Exception } + }.should raise_error(Exception) + + ScratchPad.recorded.should == [:file_opened, :file_closed] + end + + it "propagates StandardErrors produced by close" do + -> { + File.open(@file, 'r') { |f| FileSpecs.make_closer f, StandardError } + }.should raise_error(StandardError) + + ScratchPad.recorded.should == [:file_opened, :file_closed] + end + + it "does not propagate IOError with 'closed stream' message produced by close" do + File.open(@file, 'r') { |f| FileSpecs.make_closer f, IOError.new('closed stream') } + + ScratchPad.recorded.should == [:file_opened, :file_closed] + end + end + + it "opens the file (basic case)" do + @fh = File.open(@file) + @fh.should be_kind_of(File) + File.should.exist?(@file) + end + + it "opens the file with unicode characters" do + @fh = File.open(@unicode_path, "w") + @fh.should be_kind_of(File) + File.should.exist?(@unicode_path) + end + + it "opens a file when called with a block" do + File.open(@file) { |fh| } + File.should.exist?(@file) + end + + it "opens with mode string" do + @fh = File.open(@file, 'w') + @fh.should be_kind_of(File) + File.should.exist?(@file) + end + + it "opens a file with mode string and block" do + File.open(@file, 'w') { |fh| } + File.should.exist?(@file) + end + + it "opens a file with mode num" do + @fh = File.open(@file, @flags) + @fh.should be_kind_of(File) + File.should.exist?(@file) + end + + it "opens a file with mode num and block" do + File.open(@file, 'w') { |fh| } + File.should.exist?(@file) + end + + it "opens a file with mode and permission as nil" do + @fh = File.open(@file, nil, nil) + @fh.should be_kind_of(File) + end + + # For this test we delete the file first to reset the perms + it "opens the file when passed mode, num and permissions" do + rm_r @file + File.umask(0011) + @fh = File.open(@file, @flags, 0755) + @fh.should be_kind_of(File) + platform_is_not :windows do + @fh.lstat.mode.to_s(8).should == "100744" + end + File.should.exist?(@file) + end + + # For this test we delete the file first to reset the perms + it "opens the file when passed mode, num, permissions and block" do + rm_r @file + File.umask(0022) + File.open(@file, "w", 0755){ |fh| } + platform_is_not :windows do + File.stat(@file).mode.to_s(8).should == "100755" + end + File.should.exist?(@file) + end + + it "creates the file and returns writable descriptor when called with 'w' mode and r-o permissions" do + # it should be possible to write to such a file via returned descriptor, + # even though the file permissions are r-r-r. + + File.open(@file, "w", 0444) { |f| f.write("test") } + File.read(@file).should == "test" + end + + platform_is_not :windows do + it "opens the existing file, does not change permissions even when they are specified" do + File.chmod(0664, @file) + orig_perms = File.stat(@file).mode.to_s(8) + File.open(@file, "w", 0444) { |f| f.write("test") } + + File.stat(@file).mode.to_s(8).should == orig_perms + File.read(@file).should == "test" + end + end + + platform_is_not :windows do + as_user do + it "creates a new write-only file when invoked with 'w' and '0222'" do + rm_r @file + File.open(@file, 'w', 0222) {} + File.readable?(@file).should == false + File.writable?(@file).should == true + end + end + end + + it "opens the file when call with fd" do + @fh = File.open(@file) + fh_copy = File.open(@fh.fileno) + fh_copy.autoclose = false + fh_copy.should be_kind_of(File) + File.should.exist?(@file) + end + + it "opens a file that no exists when use File::WRONLY mode" do + -> { File.open(@nonexistent, File::WRONLY) }.should raise_error(Errno::ENOENT) + end + + it "opens a file that no exists when use File::RDONLY mode" do + -> { File.open(@nonexistent, File::RDONLY) }.should raise_error(Errno::ENOENT) + end + + it "opens a file that no exists when use 'r' mode" do + -> { File.open(@nonexistent, 'r') }.should raise_error(Errno::ENOENT) + end + + it "opens a file that no exists when use File::EXCL mode" do + -> { File.open(@nonexistent, File::EXCL) }.should raise_error(Errno::ENOENT) + end + + it "opens a file that no exists when use File::NONBLOCK mode" do + -> { File.open(@nonexistent, File::NONBLOCK) }.should raise_error(Errno::ENOENT) + end + + platform_is_not :openbsd, :windows do + it "opens a file that no exists when use File::TRUNC mode" do + -> { File.open(@nonexistent, File::TRUNC) }.should raise_error(Errno::ENOENT) + end + end + + platform_is :openbsd, :windows do + it "does not open a file that does no exists when using File::TRUNC mode" do + -> { File.open(@nonexistent, File::TRUNC) }.should raise_error(Errno::EINVAL) + end + end + + platform_is_not :windows do + it "opens a file that no exists when use File::NOCTTY mode" do + -> { File.open(@nonexistent, File::NOCTTY) }.should raise_error(Errno::ENOENT) + end + end + + it "opens a file that no exists when use File::CREAT mode" do + @fh = File.open(@nonexistent, File::CREAT) { |f| f } + @fh.should be_kind_of(File) + File.should.exist?(@file) + end + + it "opens a file that no exists when use 'a' mode" do + @fh = File.open(@nonexistent, 'a') { |f| f } + @fh.should be_kind_of(File) + File.should.exist?(@file) + end + + it "opens a file that no exists when use 'w' mode" do + @fh = File.open(@nonexistent, 'w') { |f| f } + @fh.should be_kind_of(File) + File.should.exist?(@file) + end + + # Check the grants associated to the different open modes combinations. + it "raises an ArgumentError exception when call with an unknown mode" do + -> { File.open(@file, "q") }.should raise_error(ArgumentError) + end + + it "can read in a block when call open with RDONLY mode" do + File.open(@file, File::RDONLY) do |f| + f.gets.should == nil + end + end + + it "can read in a block when call open with 'r' mode" do + File.open(@file, "r") do |f| + f.gets.should == nil + end + end + + it "raises an IO exception when write in a block opened with RDONLY mode" do + File.open(@file, File::RDONLY) do |f| + -> { f.puts "writing ..." }.should raise_error(IOError) + end + end + + it "raises an IO exception when write in a block opened with 'r' mode" do + File.open(@file, "r") do |f| + -> { f.puts "writing ..." }.should raise_error(IOError) + end + end + + it "can't write in a block when call open with File::WRONLY||File::RDONLY mode" do + File.open(@file, File::WRONLY|File::RDONLY ) do |f| + f.puts("writing").should == nil + end + end + + it "can't read in a block when call open with File::WRONLY||File::RDONLY mode" do + -> { + File.open(@file, File::WRONLY|File::RDONLY ) do |f| + f.gets.should == nil + end + }.should raise_error(IOError) + end + + it "can write in a block when call open with WRONLY mode" do + File.open(@file, File::WRONLY) do |f| + f.puts("writing").should == nil + end + end + + it "can write in a block when call open with 'w' mode" do + File.open(@file, "w") do |f| + f.puts("writing").should == nil + end + end + + it "raises an IOError when read in a block opened with WRONLY mode" do + File.open(@file, File::WRONLY) do |f| + -> { f.gets }.should raise_error(IOError) + end + end + + it "raises an IOError when read in a block opened with 'w' mode" do + File.open(@file, "w") do |f| + -> { f.gets }.should raise_error(IOError) + end + end + + it "raises an IOError when read in a block opened with 'a' mode" do + File.open(@file, "a") do |f| + -> { f.gets }.should raise_error(IOError) + end + end + + it "raises an IOError when read in a block opened with 'a' mode" do + File.open(@file, "a") do |f| + f.puts("writing").should == nil + -> { f.gets }.should raise_error(IOError) + end + end + + it "raises an IOError when read in a block opened with 'a' mode" do + File.open(@file, File::WRONLY|File::APPEND ) do |f| + -> { f.gets }.should raise_error(IOError) + end + end + + it "raises an IOError when read in a block opened with File::WRONLY|File::APPEND mode" do + File.open(@file, File::WRONLY|File::APPEND ) do |f| + f.puts("writing").should == nil + -> { f.gets }.should raise_error(IOError) + end + end + + it "raises an IOError when write in a block opened with File::RDONLY|File::APPEND mode" do + -> { + File.open(@file, File::RDONLY|File::APPEND ) do |f| + f.puts("writing") + end + }.should raise_error(IOError) + end + + it "can read and write in a block when call open with RDWR mode" do + File.open(@file, File::RDWR) do |f| + f.gets.should == nil + f.puts("writing").should == nil + f.rewind + f.gets.should == "writing\n" + end + end + + it "can't read in a block when call open with File::EXCL mode" do + -> { + File.open(@file, File::EXCL) do |f| + f.puts("writing").should == nil + end + }.should raise_error(IOError) + end + + it "can read in a block when call open with File::EXCL mode" do + File.open(@file, File::EXCL) do |f| + f.gets.should == nil + end + end + + it "can read and write in a block when call open with File::RDWR|File::EXCL mode" do + File.open(@file, File::RDWR|File::EXCL) do |f| + f.gets.should == nil + f.puts("writing").should == nil + f.rewind + f.gets.should == "writing\n" + end + end + + it "raises an Errno::EEXIST if the file exists when open with File::CREAT|File::EXCL" do + -> { + File.open(@file, File::CREAT|File::EXCL) do |f| + f.puts("writing") + end + }.should raise_error(Errno::EEXIST) + end + + it "creates a new file when use File::WRONLY|File::APPEND mode" do + @fh = File.open(@file, File::WRONLY|File::APPEND) + @fh.should be_kind_of(File) + File.should.exist?(@file) + end + + it "opens a file when use File::WRONLY|File::APPEND mode" do + File.open(@file, File::WRONLY) do |f| + f.puts("hello file") + end + File.open(@file, File::RDWR|File::APPEND) do |f| + f.puts("bye file") + f.rewind + f.gets.should == "hello file\n" + f.gets.should == "bye file\n" + f.gets.should == nil + end + end + + it "raises an IOError if the file exists when open with File::RDONLY|File::APPEND" do + -> { + File.open(@file, File::RDONLY|File::APPEND) do |f| + f.puts("writing").should == nil + end + }.should raise_error(IOError) + end + + platform_is_not :openbsd, :windows do + it "truncates the file when passed File::TRUNC mode" do + File.open(@file, File::RDWR) { |f| f.puts "hello file" } + @fh = File.open(@file, File::TRUNC) + @fh.gets.should == nil + end + + it "can't read in a block when call open with File::TRUNC mode" do + File.open(@file, File::TRUNC) do |f| + f.gets.should == nil + end + end + end + + it "opens a file when use File::WRONLY|File::TRUNC mode" do + fh1 = File.open(@file, "w") + begin + @fh = File.open(@file, File::WRONLY|File::TRUNC) + @fh.should be_kind_of(File) + File.should.exist?(@file) + ensure + fh1.close + end + end + + platform_is_not :openbsd, :windows do + it "can't write in a block when call open with File::TRUNC mode" do + -> { + File.open(@file, File::TRUNC) do |f| + f.puts("writing") + end + }.should raise_error(IOError) + end + + it "raises an Errno::EEXIST if the file exists when open with File::RDONLY|File::TRUNC" do + -> { + File.open(@file, File::RDONLY|File::TRUNC) do |f| + f.puts("writing").should == nil + end + }.should raise_error(IOError) + end + end + + platform_is :openbsd, :windows do + it "can't write in a block when call open with File::TRUNC mode" do + -> { + File.open(@file, File::TRUNC) do |f| + f.puts("writing") + end + }.should raise_error(Errno::EINVAL) + end + + it "raises an Errno::EEXIST if the file exists when open with File::RDONLY|File::TRUNC" do + -> { + File.open(@file, File::RDONLY|File::TRUNC) do |f| + f.puts("writing").should == nil + end + }.should raise_error(Errno::EINVAL) + end + end + + platform_is_not :windows do + as_user do + it "raises an Errno::EACCES when opening non-permitted file" do + @fh = File.open(@file, "w") + @fh.chmod(000) + -> { fh1 = File.open(@file); fh1.close }.should raise_error(Errno::EACCES) + end + end + end + + as_user do + it "raises an Errno::EACCES when opening read-only file" do + @fh = File.open(@file, "w") + @fh.chmod(0444) + -> { File.open(@file, "w") }.should raise_error(Errno::EACCES) + end + end + + it "opens a file for binary read" do + @fh = File.open(@file, "rb") + @fh.should be_kind_of(File) + File.should.exist?(@file) + end + + it "opens a file for binary write" do + @fh = File.open(@file, "wb") + @fh.should be_kind_of(File) + File.should.exist?(@file) + end + + it "opens a file for read-write and truncate the file" do + File.open(@file, "w") { |f| f.puts "testing" } + File.size(@file).should > 0 + File.open(@file, "w+") do |f| + f.pos.should == 0 + f.should.eof? + end + File.size(@file).should == 0 + end + + it "opens a file for binary read-write starting at the beginning of the file" do + File.open(@file, "w") { |f| f.puts "testing" } + File.size(@file).should > 0 + File.open(@file, "rb+") do |f| + f.binmode?.should == true + f.external_encoding.should == Encoding::ASCII_8BIT + f.pos.should == 0 + f.should_not.eof? + end + File.open(@file, "r+b") do |f| + f.binmode?.should == true + f.external_encoding.should == Encoding::ASCII_8BIT + f.pos.should == 0 + f.should_not.eof? + end + end + + it "opens a file for binary read-write and truncate the file" do + File.open(@file, "w") { |f| f.puts "testing" } + File.size(@file).should > 0 + File.open(@file, "wb+") do |f| + f.pos.should == 0 + f.should.eof? + end + File.size(@file).should == 0 + end + + platform_is :linux do + guard -> { defined?(File::TMPFILE) } do + it "creates an unnamed temporary file with File::TMPFILE" do + dir = tmp("tmpfilespec") + mkdir_p dir + begin + Dir["#{dir}/*"].should == [] + File.open(dir, "r+", flags: File::TMPFILE) do |io| + io.write("ruby") + io.flush + io.rewind + io.read.should == "ruby" + Dir["#{dir}/*"].should == [] + end + rescue Errno::EOPNOTSUPP + skip "no support from the filesystem" + rescue Errno::EINVAL, Errno::EISDIR + skip "presumably bug in glibc" + ensure + rm_r dir + end + end + end + end + + it "raises a TypeError if passed a filename that is not a String or Integer type" do + -> { File.open(true) }.should raise_error(TypeError) + -> { File.open(false) }.should raise_error(TypeError) + -> { File.open(nil) }.should raise_error(TypeError) + end + + it "raises a SystemCallError if passed an invalid Integer type" do + -> { File.open(-1) }.should raise_error(SystemCallError) + end + + it "raises an ArgumentError if passed the wrong number of arguments" do + -> { File.open(@file, File::CREAT, 0755, 'test') }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError if passed an invalid string for mode" do + -> { File.open(@file, 'fake') }.should raise_error(ArgumentError) + end + + it "defaults external_encoding to BINARY for binary modes" do + File.open(@file, 'rb') {|f| f.external_encoding.should == Encoding::BINARY} + File.open(@file, 'wb+') {|f| f.external_encoding.should == Encoding::BINARY} + end + + it "accepts options as a keyword argument" do + @fh = File.open(@file, 'w', 0755, flags: File::CREAT) + @fh.should be_an_instance_of(File) + + -> { + File.open(@file, 'w', 0755, {flags: File::CREAT}) + }.should raise_error(ArgumentError, "wrong number of arguments (given 4, expected 1..3)") + end + + it "uses the second argument as an options Hash" do + @fh = File.open(@file, mode: "r") + @fh.should be_an_instance_of(File) + end + + it "calls #to_hash to convert the second argument to a Hash" do + options = mock("file open options") + options.should_receive(:to_hash).and_return({ mode: "r" }) + + @fh = File.open(@file, **options) + end + + it "accepts extra flags as a keyword argument and combine with a string mode" do + -> { + File.open(@file, "w", flags: File::EXCL) { } + }.should raise_error(Errno::EEXIST) + + -> { + File.open(@file, mode: "w", flags: File::EXCL) { } + }.should raise_error(Errno::EEXIST) + end + + it "accepts extra flags as a keyword argument and combine with an integer mode" do + -> { + File.open(@file, File::WRONLY | File::CREAT, flags: File::EXCL) { } + }.should raise_error(Errno::EEXIST) + end + + platform_is_not :windows do + describe "on a FIFO" do + before :each do + @fifo = tmp("File_open_fifo") + File.mkfifo(@fifo) + end + + after :each do + rm_r @fifo + end + + it "opens it as a normal file" do + file_w, file_r, read_bytes, written_length = nil + + # open in threads, due to blocking open and writes + writer = Thread.new do + file_w = File.open(@fifo, 'w') + written_length = file_w.syswrite('hello') + end + reader = Thread.new do + file_r = File.open(@fifo, 'r') + read_bytes = file_r.sysread(5) + end + + begin + writer.join + reader.join + + written_length.should == 5 + read_bytes.should == 'hello' + ensure + file_w.close if file_w + file_r.close if file_r + end + end + end + end + + it "raises ArgumentError if mixing :newline and binary mode" do + -> { + File.open(@file, "rb", newline: :universal) {} + }.should raise_error(ArgumentError, "newline decorator with binary mode") + end + + context "'x' flag" do + before :each do + @xfile = tmp("x-flag") + rm_r @xfile + end + + after :each do + rm_r @xfile + end + + it "does nothing if the file doesn't exist" do + File.open(@xfile, "wx") { |f| f.write("content") } + File.read(@xfile).should == "content" + end + + it "throws a Errno::EEXIST error if the file exists" do + touch @xfile + -> { File.open(@xfile, "wx") }.should raise_error(Errno::EEXIST) + end + + it "can't be used with 'r' and 'a' flags" do + -> { File.open(@xfile, "rx") }.should raise_error(ArgumentError, 'invalid access mode rx') + -> { File.open(@xfile, "ax") }.should raise_error(ArgumentError, 'invalid access mode ax') + end + end +end + +describe "File.open when passed a file descriptor" do + before do + @content = "File#open when passed a file descriptor" + @name = tmp("file_open_with_fd.txt") + @fd = new_fd @name, "w:utf-8" + @file = nil + end + + after do + @file.close if @file and not @file.closed? + rm_r @name + end + + it "opens a file" do + @file = File.open(@fd, "w") + @file.should be_an_instance_of(File) + @file.fileno.should equal(@fd) + @file.write @content + @file.flush + File.read(@name).should == @content + end + + it "opens a file when passed a block" do + @file = File.open(@fd, "w") do |f| + f.should be_an_instance_of(File) + f.fileno.should equal(@fd) + f.write @content + f + end + File.read(@name).should == @content + end +end + +platform_is_not :windows do + describe "File.open" do + it_behaves_like :open_directory, :open + end +end diff --git a/spec/ruby/core/file/owned_spec.rb b/spec/ruby/core/file/owned_spec.rb new file mode 100644 index 0000000000..06d6796da9 --- /dev/null +++ b/spec/ruby/core/file/owned_spec.rb @@ -0,0 +1,35 @@ +require_relative '../../spec_helper' +require_relative '../../shared/file/owned' + +describe "File.owned?" do + it_behaves_like :file_owned, :owned?, File +end + +describe "File.owned?" do + before :each do + @filename = tmp("i_exist") + touch(@filename) + end + + after :each do + rm_r @filename + end + + it "returns false if file does not exist" do + File.owned?("I_am_a_bogus_file").should == false + end + + it "returns true if the file exist and is owned by the user" do + File.owned?(@filename).should == true + end + + platform_is_not :windows do + as_user do + it "returns false when the file is not owned by the user" do + system_file = '/etc/passwd' + File.owned?(system_file).should == false + end + end + end + +end diff --git a/spec/ruby/core/file/path_spec.rb b/spec/ruby/core/file/path_spec.rb new file mode 100644 index 0000000000..726febcc2b --- /dev/null +++ b/spec/ruby/core/file/path_spec.rb @@ -0,0 +1,81 @@ +require_relative '../../spec_helper' +require_relative 'shared/path' + +describe "File#path" do + it_behaves_like :file_path, :path +end + +describe "File.path" do + before :each do + @name = tmp("file_path") + end + + after :each do + rm_r @name + end + + it "returns the string argument without any change" do + File.path("abc").should == "abc" + File.path("./abc").should == "./abc" + File.path("../abc").should == "../abc" + File.path("/./a/../bc").should == "/./a/../bc" + end + + it "returns path for File argument" do + File.open(@name, "w") do |f| + File.path(f).should == @name + end + end + + it "returns path for Pathname argument" do + require "pathname" + File.path(Pathname.new(@name)).should == @name + end + + it "calls #to_path for non-string argument and returns result" do + path = mock("path") + path.should_receive(:to_path).and_return("abc") + File.path(path).should == "abc" + end + + it "raises TypeError when #to_path result is not a string" do + path = mock("path") + path.should_receive(:to_path).and_return(nil) + -> { File.path(path) }.should raise_error TypeError + + path = mock("path") + path.should_receive(:to_path).and_return(42) + -> { File.path(path) }.should raise_error TypeError + end + + it "raises ArgumentError for string argument contains NUL character" do + -> { File.path("\0") }.should raise_error ArgumentError + -> { File.path("a\0") }.should raise_error ArgumentError + -> { File.path("a\0c") }.should raise_error ArgumentError + end + + it "raises ArgumentError when #to_path result contains NUL character" do + path = mock("path") + path.should_receive(:to_path).and_return("\0") + -> { File.path(path) }.should raise_error ArgumentError + + path = mock("path") + path.should_receive(:to_path).and_return("a\0") + -> { File.path(path) }.should raise_error ArgumentError + + path = mock("path") + path.should_receive(:to_path).and_return("a\0c") + -> { File.path(path) }.should raise_error ArgumentError + end + + it "raises Encoding::CompatibilityError for ASCII-incompatible string argument" do + path = "abc".encode(Encoding::UTF_32BE) + -> { File.path(path) }.should raise_error Encoding::CompatibilityError + end + + it "raises Encoding::CompatibilityError when #to_path result is ASCII-incompatible" do + path = mock("path") + path.should_receive(:to_path).and_return("abc".encode(Encoding::UTF_32BE)) + -> { File.path(path) }.should raise_error Encoding::CompatibilityError + end +end diff --git a/spec/ruby/core/file/pipe_spec.rb b/spec/ruby/core/file/pipe_spec.rb new file mode 100644 index 0000000000..01d72dbe85 --- /dev/null +++ b/spec/ruby/core/file/pipe_spec.rb @@ -0,0 +1,32 @@ +require_relative '../../spec_helper' +require_relative '../../shared/file/pipe' + +describe "File.pipe?" do + it_behaves_like :file_pipe, :pipe?, File +end + +describe "File.pipe?" do + it "returns false if file does not exist" do + File.pipe?("I_am_a_bogus_file").should == false + end + + it "returns false if the file is not a pipe" do + filename = tmp("i_exist") + touch(filename) + + File.pipe?(filename).should == false + + rm_r filename + end + + platform_is_not :windows do + it "returns true if the file is a pipe" do + filename = tmp("i_am_a_pipe") + File.mkfifo(filename) + + File.pipe?(filename).should == true + + rm_r filename + end + end +end diff --git a/spec/ruby/core/file/printf_spec.rb b/spec/ruby/core/file/printf_spec.rb new file mode 100644 index 0000000000..2530419fc7 --- /dev/null +++ b/spec/ruby/core/file/printf_spec.rb @@ -0,0 +1,18 @@ +require_relative '../../spec_helper' +require_relative '../kernel/shared/sprintf' + +describe "File#printf" do + it_behaves_like :kernel_sprintf, -> format, *args { + begin + @filename = tmp("printf.txt") + + File.open(@filename, "w", encoding: "utf-8") do |f| + f.printf(format, *args) + end + + File.read(@filename, encoding: "utf-8") + ensure + rm_r @filename + end + } +end diff --git a/spec/ruby/core/file/read_spec.rb b/spec/ruby/core/file/read_spec.rb new file mode 100644 index 0000000000..67a3325cbd --- /dev/null +++ b/spec/ruby/core/file/read_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/read' + +describe "File.read" do + it_behaves_like :file_read_directory, :read, File +end diff --git a/spec/ruby/core/file/readable_real_spec.rb b/spec/ruby/core/file/readable_real_spec.rb new file mode 100644 index 0000000000..524466cd96 --- /dev/null +++ b/spec/ruby/core/file/readable_real_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative '../../shared/file/readable_real' + +describe "File.readable_real?" do + it_behaves_like :file_readable_real, :readable_real?, File + it_behaves_like :file_readable_real_missing, :readable_real?, File +end diff --git a/spec/ruby/core/file/readable_spec.rb b/spec/ruby/core/file/readable_spec.rb new file mode 100644 index 0000000000..ed75a23f39 --- /dev/null +++ b/spec/ruby/core/file/readable_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative '../../shared/file/readable' + +describe "File.readable?" do + it_behaves_like :file_readable, :readable?, File + it_behaves_like :file_readable_missing, :readable?, File +end diff --git a/spec/ruby/core/file/readlink_spec.rb b/spec/ruby/core/file/readlink_spec.rb new file mode 100644 index 0000000000..20741ba121 --- /dev/null +++ b/spec/ruby/core/file/readlink_spec.rb @@ -0,0 +1,86 @@ +require_relative '../../spec_helper' + +describe "File.readlink" do + # symlink/readlink are not supported on Windows + platform_is_not :windows do + describe "with absolute paths" do + before :each do + @file = tmp('file_readlink.txt') + @link = tmp('file_readlink.lnk') + + File.symlink(@file, @link) + end + + after :each do + rm_r @file, @link + end + + it "returns the name of the file referenced by the given link" do + touch @file + File.readlink(@link).should == @file + end + + it "returns the name of the file referenced by the given link when the file does not exist" do + File.readlink(@link).should == @file + end + + it "raises an Errno::ENOENT if there is no such file" do + # TODO: missing_file + -> { File.readlink("/this/surely/does/not/exist") }.should raise_error(Errno::ENOENT) + end + + it "raises an Errno::EINVAL if called with a normal file" do + touch @file + -> { File.readlink(@file) }.should raise_error(Errno::EINVAL) + end + end + + describe "with paths containing unicode characters" do + before :each do + @file = tmp('tàrget.txt') + @link = tmp('lïnk.lnk') + File.symlink(@file, @link) + end + + after :each do + rm_r @file, @link + end + + it "returns the name of the file referenced by the given link" do + touch @file + result = File.readlink(@link) + result.encoding.should equal Encoding.find('filesystem') + result.should == @file.dup.force_encoding(Encoding.find('filesystem')) + end + end + + describe "when changing the working directory" do + before :each do + @cwd = Dir.pwd + @tmpdir = tmp("/readlink") + Dir.mkdir @tmpdir + Dir.chdir @tmpdir + + @link = 'readlink_link' + @file = 'readlink_file' + + File.symlink(@file, @link) + end + + after :each do + rm_r @file, @link + Dir.chdir @cwd + Dir.rmdir @tmpdir + end + + it "returns the name of the file referenced by the given link" do + touch @file + File.readlink(@link).should == @file + end + + it "returns the name of the file referenced by the given link when the file does not exist" do + File.readlink(@link).should == @file + end + end + end +end diff --git a/spec/ruby/core/file/realdirpath_spec.rb b/spec/ruby/core/file/realdirpath_spec.rb new file mode 100644 index 0000000000..74053afce3 --- /dev/null +++ b/spec/ruby/core/file/realdirpath_spec.rb @@ -0,0 +1,104 @@ +require_relative '../../spec_helper' + +platform_is_not :windows do + describe "File.realdirpath" do + before :each do + @real_dir = tmp('dir_realdirpath_real') + @fake_dir = tmp('dir_realdirpath_fake') + @link_dir = tmp('dir_realdirpath_link') + + mkdir_p @real_dir + File.symlink(@real_dir, @link_dir) + + @file = File.join(@real_dir, 'file') + @link = File.join(@link_dir, 'link') + + touch @file + File.symlink(@file, @link) + + @fake_file_in_real_dir = File.join(@real_dir, 'fake_file_in_real_dir') + @fake_file_in_fake_dir = File.join(@fake_dir, 'fake_file_in_fake_dir') + @fake_link_to_real_dir = File.join(@link_dir, 'fake_link_to_real_dir') + @fake_link_to_fake_dir = File.join(@link_dir, 'fake_link_to_fake_dir') + + File.symlink(@fake_file_in_real_dir, @fake_link_to_real_dir) + File.symlink(@fake_file_in_fake_dir, @fake_link_to_fake_dir) + + @dir_for_relative_link = File.join(@real_dir, 'dir1') + mkdir_p @dir_for_relative_link + + @relative_path_to_file = File.join('..', 'file') + @relative_symlink = File.join(@dir_for_relative_link, 'link') + File.symlink(@relative_path_to_file, @relative_symlink) + end + + after :each do + rm_r @file, @link, @fake_link_to_real_dir, @fake_link_to_fake_dir, @real_dir, @link_dir + end + + it "returns '/' when passed '/'" do + File.realdirpath('/').should == '/' + end + + it "returns the real (absolute) pathname not containing symlinks" do + File.realdirpath(@link).should == @file + end + + it "uses base directory for interpreting relative pathname" do + File.realdirpath(File.basename(@link), @link_dir).should == @file + end + + it "uses current directory for interpreting relative pathname" do + Dir.chdir @link_dir do + File.realdirpath(File.basename(@link)).should == @file + end + end + + it "uses link directory for expanding relative links" do + File.realdirpath(@relative_symlink).should == @file + end + + it "raises an Errno::ELOOP if the symlink points to itself" do + File.unlink @link + File.symlink(@link, @link) + -> { File.realdirpath(@link) }.should raise_error(Errno::ELOOP) + end + + it "returns the real (absolute) pathname if the file is absent" do + File.realdirpath(@fake_file_in_real_dir).should == @fake_file_in_real_dir + end + + it "raises Errno::ENOENT if the directory is absent" do + -> { File.realdirpath(@fake_file_in_fake_dir) }.should raise_error(Errno::ENOENT) + end + + it "returns the real (absolute) pathname if the symlink points to an absent file" do + File.realdirpath(@fake_link_to_real_dir).should == @fake_file_in_real_dir + end + + it "raises Errno::ENOENT if the symlink points to an absent directory" do + -> { File.realdirpath(@fake_link_to_fake_dir) }.should raise_error(Errno::ENOENT) + end + end +end + +platform_is :windows do + describe "File.realdirpath" do + before :each do + @file = tmp("realdirpath") + end + + after :each do + rm_r @file + end + + it "returns the same path" do + touch @file + File.realdirpath(@file).should == @file + end + + it "returns the same path even if the last component does not exist" do + File.realdirpath(@file).should == @file + end + end +end diff --git a/spec/ruby/core/file/realpath_spec.rb b/spec/ruby/core/file/realpath_spec.rb new file mode 100644 index 0000000000..bd25bfdecf --- /dev/null +++ b/spec/ruby/core/file/realpath_spec.rb @@ -0,0 +1,98 @@ +require_relative '../../spec_helper' + +platform_is_not :windows do + describe "File.realpath" do + before :each do + @real_dir = tmp('dir_realpath_real') + @link_dir = tmp('dir_realpath_link') + + mkdir_p @real_dir + File.symlink(@real_dir, @link_dir) + + @file = File.join(@real_dir, 'file') + @link = File.join(@link_dir, 'link') + + touch @file + File.symlink(@file, @link) + + @fake_file = File.join(@real_dir, 'fake_file') + @fake_link = File.join(@link_dir, 'fake_link') + + File.symlink(@fake_file, @fake_link) + + @dir_for_relative_link = File.join(@real_dir, 'dir1') + mkdir_p @dir_for_relative_link + + @relative_path_to_file = File.join('..', 'file') + @relative_symlink = File.join(@dir_for_relative_link, 'link') + File.symlink(@relative_path_to_file, @relative_symlink) + end + + after :each do + rm_r @file, @link, @fake_link, @real_dir, @link_dir + end + + it "returns '/' when passed '/'" do + File.realpath('/').should == '/' + end + + it "returns the real (absolute) pathname not containing symlinks" do + File.realpath(@link).should == @file + end + + it "uses base directory for interpreting relative pathname" do + File.realpath(File.basename(@link), @link_dir).should == @file + end + + it "uses current directory for interpreting relative pathname" do + Dir.chdir @link_dir do + File.realpath(File.basename(@link)).should == @file + end + end + + it "uses link directory for expanding relative links" do + File.realpath(@relative_symlink).should == @file + end + + it "removes the file element when going one level up" do + File.realpath('../', @file).should == @real_dir + end + + it "raises an Errno::ELOOP if the symlink points to itself" do + File.unlink @link + File.symlink(@link, @link) + -> { File.realpath(@link) }.should raise_error(Errno::ELOOP) + end + + it "raises Errno::ENOENT if the file is absent" do + -> { File.realpath(@fake_file) }.should raise_error(Errno::ENOENT) + end + + it "raises Errno::ENOENT if the symlink points to an absent file" do + -> { File.realpath(@fake_link) }.should raise_error(Errno::ENOENT) + end + + it "converts the argument with #to_path" do + path = mock("path") + path.should_receive(:to_path).and_return(__FILE__) + File.realpath(path).should == File.realpath(__FILE__ ) + end + end +end + +platform_is :windows do + describe "File.realpath" do + before :each do + @file = tmp("realpath") + touch @file + end + + after :each do + rm_r @file + end + + it "returns the same path" do + File.realpath(@file).should == @file + end + end +end diff --git a/spec/ruby/core/file/rename_spec.rb b/spec/ruby/core/file/rename_spec.rb new file mode 100644 index 0000000000..f2c18d4905 --- /dev/null +++ b/spec/ruby/core/file/rename_spec.rb @@ -0,0 +1,37 @@ +require_relative '../../spec_helper' + +describe "File.rename" do + before :each do + @old = tmp("file_rename.txt") + @new = tmp("file_rename.new") + + rm_r @new + touch(@old) { |f| f.puts "hello" } + end + + after :each do + rm_r @old, @new + end + + it "renames a file" do + File.should.exist?(@old) + File.should_not.exist?(@new) + File.rename(@old, @new) + File.should_not.exist?(@old) + File.should.exist?(@new) + end + + it "raises an Errno::ENOENT if the source does not exist" do + rm_r @old + -> { File.rename(@old, @new) }.should raise_error(Errno::ENOENT) + end + + it "raises an ArgumentError if not passed two arguments" do + -> { File.rename }.should raise_error(ArgumentError) + -> { File.rename(@file) }.should raise_error(ArgumentError) + end + + it "raises a TypeError if not passed String types" do + -> { File.rename(1, 2) }.should raise_error(TypeError) + end +end diff --git a/spec/ruby/core/file/reopen_spec.rb b/spec/ruby/core/file/reopen_spec.rb new file mode 100644 index 0000000000..858d424c67 --- /dev/null +++ b/spec/ruby/core/file/reopen_spec.rb @@ -0,0 +1,32 @@ +require_relative '../../spec_helper' + +describe "File#reopen" do + before :each do + @name_a = tmp("file_reopen_a.txt") + @name_b = tmp("file_reopen_b.txt") + @content_a = "File#reopen a" + @content_b = "File#reopen b" + + touch(@name_a) { |f| f.write @content_a } + touch(@name_b) { |f| f.write @content_b } + + @file = nil + end + + after :each do + @file.close if @file and not @file.closed? + rm_r @name_a, @name_b + end + + it "resets the stream to a new file path" do + file = File.new @name_a, "r" + file.read.should == @content_a + @file = file.reopen(@name_b, "r") + @file.read.should == @content_b + end + + it "calls #to_path to convert an Object" do + @file = File.new(@name_a).reopen(mock_to_path(@name_b), "r") + @file.read.should == @content_b + end +end diff --git a/spec/ruby/core/file/setgid_spec.rb b/spec/ruby/core/file/setgid_spec.rb new file mode 100644 index 0000000000..f5df5390f5 --- /dev/null +++ b/spec/ruby/core/file/setgid_spec.rb @@ -0,0 +1,36 @@ +require_relative '../../spec_helper' +require_relative '../../shared/file/setgid' + +describe "File.setgid?" do + it_behaves_like :file_setgid, :setgid?, File +end + +describe "File.setgid?" do + before :each do + @name = tmp('test.txt') + touch @name + end + + after :each do + rm_r @name + end + + it "returns false if the file was just made" do + File.setgid?(@name).should == false + end + + it "returns false if the file does not exist" do + rm_r @name # delete it prematurely, just for this part + File.setgid?(@name).should == false + end + + as_superuser do + platform_is_not :windows do + it "returns true when the gid bit is set" do + system "chmod g+s #{@name}" + + File.setgid?(@name).should == true + end + end + end +end diff --git a/spec/ruby/core/file/setuid_spec.rb b/spec/ruby/core/file/setuid_spec.rb new file mode 100644 index 0000000000..9e5e86df61 --- /dev/null +++ b/spec/ruby/core/file/setuid_spec.rb @@ -0,0 +1,34 @@ +require_relative '../../spec_helper' +require_relative '../../shared/file/setuid' + +describe "File.setuid?" do + it_behaves_like :file_setuid, :setuid?, File +end + +describe "File.setuid?" do + before :each do + @name = tmp('test.txt') + touch @name + end + + after :each do + rm_r @name + end + + it "returns false if the file was just made" do + File.setuid?(@name).should == false + end + + it "returns false if the file does not exist" do + rm_r @name # delete it prematurely, just for this part + File.setuid?(@name).should == false + end + + platform_is_not :windows do + it "returns true when the gid bit is set" do + system "chmod u+s #{@name}" + + File.setuid?(@name).should == true + end + end +end diff --git a/spec/ruby/core/file/shared/fnmatch.rb b/spec/ruby/core/file/shared/fnmatch.rb new file mode 100644 index 0000000000..db4b5c5d8c --- /dev/null +++ b/spec/ruby/core/file/shared/fnmatch.rb @@ -0,0 +1,294 @@ +describe :file_fnmatch, shared: true do + it "matches entire strings" do + File.send(@method, 'cat', 'cat').should == true + end + + it "does not match partial strings" do + File.send(@method, 'cat', 'category').should == false + end + + it "does not support { } patterns by default" do + File.send(@method, 'c{at,ub}s', 'cats').should == false + File.send(@method, 'c{at,ub}s', 'c{at,ub}s').should == true + end + + it "supports some { } patterns when File::FNM_EXTGLOB is passed" do + File.send(@method, "{a,b}", "a", File::FNM_EXTGLOB).should == true + File.send(@method, "{a,b}", "b", File::FNM_EXTGLOB).should == true + File.send(@method, "c{at,ub}s", "cats", File::FNM_EXTGLOB).should == true + File.send(@method, "c{at,ub}s", "cubs", File::FNM_EXTGLOB).should == true + File.send(@method, "-c{at,ub}s-", "-cats-", File::FNM_EXTGLOB).should == true + File.send(@method, "-c{at,ub}s-", "-cubs-", File::FNM_EXTGLOB).should == true + File.send(@method, "{a,b,c}{d,e,f}{g,h}", "adg", File::FNM_EXTGLOB).should == true + File.send(@method, "{a,b,c}{d,e,f}{g,h}", "bdg", File::FNM_EXTGLOB).should == true + File.send(@method, "{a,b,c}{d,e,f}{g,h}", "ceh", File::FNM_EXTGLOB).should == true + File.send(@method, "{aa,bb,cc,dd}", "aa", File::FNM_EXTGLOB).should == true + File.send(@method, "{aa,bb,cc,dd}", "bb", File::FNM_EXTGLOB).should == true + File.send(@method, "{aa,bb,cc,dd}", "cc", File::FNM_EXTGLOB).should == true + File.send(@method, "{aa,bb,cc,dd}", "dd", File::FNM_EXTGLOB).should == true + File.send(@method, "{1,5{a,b{c,d}}}", "1", File::FNM_EXTGLOB).should == true + File.send(@method, "{1,5{a,b{c,d}}}", "5a", File::FNM_EXTGLOB).should == true + File.send(@method, "{1,5{a,b{c,d}}}", "5bc", File::FNM_EXTGLOB).should == true + File.send(@method, "{1,5{a,b{c,d}}}", "5bd", File::FNM_EXTGLOB).should == true + File.send(@method, "\\\\{a\\,b,b\\}c}", "\\a,b", File::FNM_EXTGLOB).should == true + File.send(@method, "\\\\{a\\,b,b\\}c}", "\\b}c", File::FNM_EXTGLOB).should == true + end + + it "doesn't support some { } patterns even when File::FNM_EXTGLOB is passed" do + File.send(@method, "a{0..3}b", "a0b", File::FNM_EXTGLOB).should == false + File.send(@method, "a{0..3}b", "a1b", File::FNM_EXTGLOB).should == false + File.send(@method, "a{0..3}b", "a2b", File::FNM_EXTGLOB).should == false + File.send(@method, "a{0..3}b", "a3b", File::FNM_EXTGLOB).should == false + File.send(@method, "{0..12}", "0", File::FNM_EXTGLOB).should == false + File.send(@method, "{0..12}", "6", File::FNM_EXTGLOB).should == false + File.send(@method, "{0..12}", "12", File::FNM_EXTGLOB).should == false + File.send(@method, "{3..-2}", "3", File::FNM_EXTGLOB).should == false + File.send(@method, "{3..-2}", "0", File::FNM_EXTGLOB).should == false + File.send(@method, "{3..-2}", "-2", File::FNM_EXTGLOB).should == false + File.send(@method, "{a..g}", "a", File::FNM_EXTGLOB).should == false + File.send(@method, "{a..g}", "d", File::FNM_EXTGLOB).should == false + File.send(@method, "{a..g}", "g", File::FNM_EXTGLOB).should == false + File.send(@method, "{g..a}", "a", File::FNM_EXTGLOB).should == false + File.send(@method, "{g..a}", "d", File::FNM_EXTGLOB).should == false + File.send(@method, "{g..a}", "g", File::FNM_EXTGLOB).should == false + File.send(@method, "escaping: {{,\\,,\\},\\{}", "escaping: {", File::FNM_EXTGLOB).should == false + File.send(@method, "escaping: {{,\\,,\\},\\{}", "escaping: ,", File::FNM_EXTGLOB).should == false + File.send(@method, "escaping: {{,\\,,\\},\\{}", "escaping: }", File::FNM_EXTGLOB).should == false + File.send(@method, "escaping: {{,\\,,\\},\\{}", "escaping: {", File::FNM_EXTGLOB).should == false + end + + it "doesn't match an extra } when File::FNM_EXTGLOB is passed" do + File.send(@method, 'c{at,ub}}s', 'cats', File::FNM_EXTGLOB).should == false + end + + it "matches when both FNM_EXTGLOB and FNM_PATHNAME are passed" do + File.send(@method, "?.md", "a.md", File::FNM_EXTGLOB | File::FNM_PATHNAME).should == true + end + + it "matches a single character for each ? character" do + File.send(@method, 'c?t', 'cat').should == true + File.send(@method, 'c??t', 'cat').should == false + end + + it "matches zero or more characters for each * character" do + File.send(@method, 'c*', 'cats').should == true + File.send(@method, 'c*t', 'c/a/b/t').should == true + end + + it "does not match unterminated range of characters" do + File.send(@method, 'abc[de', 'abcd').should == false + end + + it "does not match unterminated range of characters as a literal" do + File.send(@method, 'abc[de', 'abc[de').should == false + end + + it "matches ranges of characters using bracket expression (e.g. [a-z])" do + File.send(@method, 'ca[a-z]', 'cat').should == true + end + + it "matches ranges of characters using bracket expression, taking case into account" do + File.send(@method, '[a-z]', 'D').should == false + File.send(@method, '[^a-z]', 'D').should == true + File.send(@method, '[A-Z]', 'd').should == false + File.send(@method, '[^A-Z]', 'd').should == true + File.send(@method, '[a-z]', 'D', File::FNM_CASEFOLD).should == true + end + + it "does not match characters outside of the range of the bracket expression" do + File.send(@method, 'ca[x-z]', 'cat').should == false + File.send(@method, '/ca[s][s-t]/rul[a-b]/[z]he/[x-Z]orld', '/cats/rule/the/World').should == false + end + + it "matches ranges of characters using exclusive bracket expression (e.g. [^t] or [!t])" do + File.send(@method, 'ca[^t]', 'cat').should == false + File.send(@method, 'ca[^t]', 'cas').should == true + File.send(@method, 'ca[!t]', 'cat').should == false + end + + it "matches characters with a case sensitive comparison" do + File.send(@method, 'cat', 'CAT').should == false + end + + it "matches characters with case insensitive comparison when flags includes FNM_CASEFOLD" do + File.send(@method, 'cat', 'CAT', File::FNM_CASEFOLD).should == true + end + + platform_is_not :windows do + it "doesn't match case sensitive characters on platforms with case sensitive paths, when flags include FNM_SYSCASE" do + File.send(@method, 'cat', 'CAT', File::FNM_SYSCASE).should == false + end + end + + platform_is :windows do + it "matches case sensitive characters on platforms with case insensitive paths, when flags include FNM_SYSCASE" do + File.send(@method, 'cat', 'CAT', File::FNM_SYSCASE).should == true + end + end + + it "matches wildcard with characters when flags includes FNM_PATHNAME" do + File.send(@method, '*a', 'aa', File::FNM_PATHNAME).should == true + File.send(@method, 'a*', 'aa', File::FNM_PATHNAME).should == true + File.send(@method, 'a*', 'aaa', File::FNM_PATHNAME).should == true + File.send(@method, '*a', 'aaa', File::FNM_PATHNAME).should == true + end + + it "does not match '/' characters with ? or * when flags includes FNM_PATHNAME" do + File.send(@method, '?', '/', File::FNM_PATHNAME).should == false + File.send(@method, '*', '/', File::FNM_PATHNAME).should == false + end + + it "does not match '/' characters inside bracket expressions when flags includes FNM_PATHNAME" do + File.send(@method, '[/]', '/', File::FNM_PATHNAME).should == false + end + + it "matches literal ? or * in path when pattern includes \\? or \\*" do + File.send(@method, '\?', '?').should == true + File.send(@method, '\?', 'a').should == false + + File.send(@method, '\*', '*').should == true + File.send(@method, '\*', 'a').should == false + end + + it "matches literal character (e.g. 'a') in path when pattern includes escaped character (e.g. \\a)" do + File.send(@method, '\a', 'a').should == true + File.send(@method, 'this\b', 'thisb').should == true + end + + it "matches '\\' characters in path when flags includes FNM_NOESACPE" do + File.send(@method, '\a', '\a', File::FNM_NOESCAPE).should == true + File.send(@method, '\a', 'a', File::FNM_NOESCAPE).should == false + File.send(@method, '\[foo\]\[bar\]', '[foo][bar]', File::FNM_NOESCAPE).should == false + end + + it "escapes special characters inside bracket expression" do + File.send(@method, '[\?]', '?').should == true + File.send(@method, '[\*]', '*').should == true + end + + it "does not match leading periods in filenames with wildcards by default" do + File.should_not.send(@method, '*', '.profile') + File.should.send(@method, '*', 'home/.profile') + File.should.send(@method, '*/*', 'home/.profile') + File.should_not.send(@method, '*/*', 'dave/.profile', File::FNM_PATHNAME) + end + + it "matches patterns with leading periods to dotfiles" do + File.send(@method, '.*', '.profile').should == true + File.send(@method, '.*', '.profile', File::FNM_PATHNAME).should == true + File.send(@method, ".*file", "nondotfile").should == false + File.send(@method, ".*file", "nondotfile", File::FNM_PATHNAME).should == false + end + + it "does not match directories with leading periods by default with FNM_PATHNAME" do + File.send(@method, '.*', '.directory/nondotfile', File::FNM_PATHNAME).should == false + File.send(@method, '.*', '.directory/.profile', File::FNM_PATHNAME).should == false + File.send(@method, '.*', 'foo/.directory/nondotfile', File::FNM_PATHNAME).should == false + File.send(@method, '.*', 'foo/.directory/.profile', File::FNM_PATHNAME).should == false + File.send(@method, '**/.dotfile', '.dotsubdir/.dotfile', File::FNM_PATHNAME).should == false + end + + it "matches leading periods in filenames when flags includes FNM_DOTMATCH" do + File.send(@method, '*', '.profile', File::FNM_DOTMATCH).should == true + File.send(@method, '*', 'home/.profile', File::FNM_DOTMATCH).should == true + end + + it "matches multiple directories with ** and *" do + files = '**/*.rb' + File.send(@method, files, 'main.rb').should == false + File.send(@method, files, './main.rb').should == false + File.send(@method, files, 'lib/song.rb').should == true + File.send(@method, '**.rb', 'main.rb').should == true + File.send(@method, '**.rb', './main.rb').should == false + File.send(@method, '**.rb', 'lib/song.rb').should == true + File.send(@method, '*', 'dave/.profile').should == true + end + + it "matches multiple directories with ** when flags includes File::FNM_PATHNAME" do + files = '**/*.rb' + flags = File::FNM_PATHNAME + + File.send(@method, files, 'main.rb', flags).should == true + File.send(@method, files, 'one/two/three/main.rb', flags).should == true + File.send(@method, files, './main.rb', flags).should == false + + flags = File::FNM_PATHNAME | File::FNM_DOTMATCH + + File.send(@method, files, './main.rb', flags).should == true + File.send(@method, files, 'one/two/.main.rb', flags).should == true + + File.send(@method, "**/best/*", 'lib/my/best/song.rb').should == true + end + + it "returns false if '/' in pattern do not match '/' in path when flags includes FNM_PATHNAME" do + pattern = '*/*' + File.send(@method, pattern, 'dave/.profile', File::FNM_PATHNAME).should be_false + + pattern = '**/foo' + File.send(@method, pattern, 'a/.b/c/foo', File::FNM_PATHNAME).should be_false + end + + it "returns true if '/' in pattern match '/' in path when flags includes FNM_PATHNAME" do + pattern = '*/*' + File.send(@method, pattern, 'dave/.profile', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_true + + pattern = '**/foo' + File.send(@method, pattern, 'a/b/c/foo', File::FNM_PATHNAME).should be_true + File.send(@method, pattern, '/a/b/c/foo', File::FNM_PATHNAME).should be_true + File.send(@method, pattern, 'c:/a/b/c/foo', File::FNM_PATHNAME).should be_true + File.send(@method, pattern, 'a/.b/c/foo', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_true + end + + it "has special handling for ./ when using * and FNM_PATHNAME" do + File.send(@method, './*', '.', File::FNM_PATHNAME).should be_false + File.send(@method, './*', './', File::FNM_PATHNAME).should be_true + File.send(@method, './*/', './', File::FNM_PATHNAME).should be_false + File.send(@method, './**', './', File::FNM_PATHNAME).should be_true + File.send(@method, './**/', './', File::FNM_PATHNAME).should be_true + File.send(@method, './*', '.', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_false + File.send(@method, './*', './', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_true + File.send(@method, './*/', './', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_false + File.send(@method, './**', './', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_true + File.send(@method, './**/', './', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_true + end + + it "matches **/* with FNM_PATHNAME to recurse directories" do + File.send(@method, 'nested/**/*', 'nested/subdir', File::FNM_PATHNAME).should be_true + File.send(@method, 'nested/**/*', 'nested/subdir/file', File::FNM_PATHNAME).should be_true + File.send(@method, 'nested/**/*', 'nested/.dotsubdir', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_true + File.send(@method, 'nested/**/*', 'nested/.dotsubir/.dotfile', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_true + end + + it "matches ** with FNM_PATHNAME only in current directory" do + File.send(@method, 'nested/**', 'nested/subdir', File::FNM_PATHNAME).should be_true + File.send(@method, 'nested/**', 'nested/subdir/file', File::FNM_PATHNAME).should be_false + File.send(@method, 'nested/**', 'nested/.dotsubdir', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_true + File.send(@method, 'nested/**', 'nested/.dotsubir/.dotfile', File::FNM_PATHNAME | File::FNM_DOTMATCH).should be_false + end + + it "accepts an object that has a #to_path method" do + File.send(@method, '\*', mock_to_path('a')).should == false + end + + it "raises a TypeError if the first and second arguments are not string-like" do + -> { File.send(@method, nil, nil, 0, 0) }.should raise_error(ArgumentError) + -> { File.send(@method, 1, 'some/thing') }.should raise_error(TypeError) + -> { File.send(@method, 'some/thing', 1) }.should raise_error(TypeError) + -> { File.send(@method, 1, 1) }.should raise_error(TypeError) + end + + it "raises a TypeError if the third argument is not an Integer" do + -> { File.send(@method, "*/place", "path/to/file", "flags") }.should raise_error(TypeError) + -> { File.send(@method, "*/place", "path/to/file", nil) }.should raise_error(TypeError) + end + + it "does not raise a TypeError if the third argument can be coerced to an Integer" do + flags = mock("flags") + flags.should_receive(:to_int).and_return(10) + -> { File.send(@method, "*/place", "path/to/file", flags) }.should_not raise_error + end + + it "matches multibyte characters" do + File.fnmatch("*/ä/ø/ñ", "a/ä/ø/ñ").should == true + end +end diff --git a/spec/ruby/core/file/shared/open.rb b/spec/ruby/core/file/shared/open.rb new file mode 100644 index 0000000000..677a82a351 --- /dev/null +++ b/spec/ruby/core/file/shared/open.rb @@ -0,0 +1,12 @@ +require_relative '../../dir/fixtures/common' + +describe :open_directory, shared: true do + it "opens directories" do + file = File.send(@method, tmp("")) + begin + file.should be_kind_of(File) + ensure + file.close + end + end +end diff --git a/spec/ruby/core/file/shared/path.rb b/spec/ruby/core/file/shared/path.rb new file mode 100644 index 0000000000..5a9fe1b0c5 --- /dev/null +++ b/spec/ruby/core/file/shared/path.rb @@ -0,0 +1,82 @@ +describe :file_path, shared: true do + before :each do + @path = tmp("file_to_path") + @name = File.basename(@path) + touch @path + end + + after :each do + @file.close if @file and !@file.closed? + rm_r @path + end + + it "returns a String" do + @file = File.new @path + @file.send(@method).should be_an_instance_of(String) + end + + it "returns a different String on every call" do + @file = File.new @path + path1 = @file.send(@method) + path2 = @file.send(@method) + path1.should == path2 + path1.should_not.equal?(path2) + end + + it "returns a mutable String" do + @file = File.new @path.dup.freeze + path = @file.send(@method) + path.should == @path + path.should_not.frozen? + path << "test" + @file.send(@method).should == @path + end + + it "calls to_str on argument and returns exact value" do + path = mock('path') + path.should_receive(:to_str).and_return(@path) + @file = File.new path + @file.send(@method).should == @path + end + + it "does not normalise the path it returns" do + Dir.chdir(tmp("")) do + unorm = "./#{@name}" + @file = File.new unorm + @file.send(@method).should == unorm + end + end + + it "does not canonicalize the path it returns" do + dir = File.basename tmp("") + path = "#{tmp("")}../#{dir}/#{@name}" + @file = File.new path + @file.send(@method).should == path + end + + it "does not absolute-ise the path it returns" do + Dir.chdir(tmp("")) do + @file = File.new @name + @file.send(@method).should == @name + end + end + + it "preserves the encoding of the path" do + path = @path.force_encoding("euc-jp") + @file = File.new path + @file.send(@method).encoding.should == Encoding.find("euc-jp") + end + + platform_is :linux do + guard -> { defined?(File::TMPFILE) } do + before :each do + @dir = tmp("tmpfilespec") + mkdir_p @dir + end + + after :each do + rm_r @dir + end + end + end +end diff --git a/spec/ruby/core/file/shared/read.rb b/spec/ruby/core/file/shared/read.rb new file mode 100644 index 0000000000..f232235298 --- /dev/null +++ b/spec/ruby/core/file/shared/read.rb @@ -0,0 +1,15 @@ +require_relative '../../dir/fixtures/common' + +describe :file_read_directory, shared: true do + platform_is :darwin, :linux, :freebsd, :openbsd, :windows do + it "raises an Errno::EISDIR when passed a path that is a directory" do + -> { @object.send(@method, ".") }.should raise_error(Errno::EISDIR) + end + end + + platform_is :netbsd do + it "does not raises any exception when passed a path that is a directory" do + -> { @object.send(@method, ".") }.should_not raise_error + end + end +end diff --git a/spec/ruby/core/file/shared/stat.rb b/spec/ruby/core/file/shared/stat.rb new file mode 100644 index 0000000000..fdaf97ea61 --- /dev/null +++ b/spec/ruby/core/file/shared/stat.rb @@ -0,0 +1,32 @@ +describe :file_stat, shared: true do + before :each do + @file = tmp('i_exist') + touch(@file) + end + + after :each do + rm_r @file + end + + it "returns a File::Stat object if the given file exists" do + st = File.send(@method, @file) + st.should be_an_instance_of(File::Stat) + end + + it "returns a File::Stat object when called on an instance of File" do + File.open(@file) do |f| + st = f.send(@method) + st.should be_an_instance_of(File::Stat) + end + end + + it "accepts an object that has a #to_path method" do + File.send(@method, mock_to_path(@file)) + end + + it "raises an Errno::ENOENT if the file does not exist" do + -> { + File.send(@method, "fake_file") + }.should raise_error(Errno::ENOENT) + end +end diff --git a/spec/ruby/core/file/shared/unlink.rb b/spec/ruby/core/file/shared/unlink.rb new file mode 100644 index 0000000000..e339e93271 --- /dev/null +++ b/spec/ruby/core/file/shared/unlink.rb @@ -0,0 +1,61 @@ +describe :file_unlink, shared: true do + before :each do + @file1 = tmp('test.txt') + @file2 = tmp('test2.txt') + + touch @file1 + touch @file2 + end + + after :each do + File.send(@method, @file1) if File.exist?(@file1) + File.send(@method, @file2) if File.exist?(@file2) + + @file1 = nil + @file2 = nil + end + + it "returns 0 when called without arguments" do + File.send(@method).should == 0 + end + + it "deletes a single file" do + File.send(@method, @file1).should == 1 + File.should_not.exist?(@file1) + end + + it "deletes multiple files" do + File.send(@method, @file1, @file2).should == 2 + File.should_not.exist?(@file1) + File.should_not.exist?(@file2) + end + + it "raises a TypeError if not passed a String type" do + -> { File.send(@method, 1) }.should raise_error(TypeError) + end + + it "raises an Errno::ENOENT when the given file doesn't exist" do + -> { File.send(@method, 'bogus') }.should raise_error(Errno::ENOENT) + end + + it "coerces a given parameter into a string if possible" do + mock = mock("to_str") + mock.should_receive(:to_str).and_return(@file1) + File.send(@method, mock).should == 1 + end + + it "accepts an object that has a #to_path method" do + File.send(@method, mock_to_path(@file1)).should == 1 + end + + platform_is :windows do + it "allows deleting an open file with File::SHARE_DELETE" do + path = tmp("share_delete.txt") + File.open(path, mode: File::CREAT | File::WRONLY | File::BINARY | File::SHARE_DELETE) do |f| + File.should.exist?(path) + File.send(@method, path) + end + File.should_not.exist?(path) + end + end +end diff --git a/spec/ruby/core/file/shared/update_time.rb b/spec/ruby/core/file/shared/update_time.rb new file mode 100644 index 0000000000..3fe7266a00 --- /dev/null +++ b/spec/ruby/core/file/shared/update_time.rb @@ -0,0 +1,105 @@ +describe :update_time, shared: true do + before :all do + @time_is_float = platform_is :windows + end + + before :each do + @atime = Time.now + @mtime = Time.now + @file1 = tmp("specs_file_utime1") + @file2 = tmp("specs_file_utime2") + touch @file1 + touch @file2 + end + + after :each do + rm_r @file1, @file2 + end + + it "sets the access and modification time of each file" do + File.send(@method, @atime, @mtime, @file1, @file2) + + if @time_is_float + File.atime(@file1).should be_close(@atime, 0.0001) + File.mtime(@file1).should be_close(@mtime, 0.0001) + File.atime(@file2).should be_close(@atime, 0.0001) + File.mtime(@file2).should be_close(@mtime, 0.0001) + else + File.atime(@file1).to_i.should be_close(@atime.to_i, TIME_TOLERANCE) + File.mtime(@file1).to_i.should be_close(@mtime.to_i, TIME_TOLERANCE) + File.atime(@file2).to_i.should be_close(@atime.to_i, TIME_TOLERANCE) + File.mtime(@file2).to_i.should be_close(@mtime.to_i, TIME_TOLERANCE) + end + end + + it "uses the current times if two nil values are passed" do + tn = Time.now + File.send(@method, nil, nil, @file1, @file2) + + if @time_is_float + File.atime(@file1).should be_close(tn, 0.050) + File.mtime(@file1).should be_close(tn, 0.050) + File.atime(@file2).should be_close(tn, 0.050) + File.mtime(@file2).should be_close(tn, 0.050) + else + File.atime(@file1).to_i.should be_close(Time.now.to_i, TIME_TOLERANCE) + File.mtime(@file1).to_i.should be_close(Time.now.to_i, TIME_TOLERANCE) + File.atime(@file2).to_i.should be_close(Time.now.to_i, TIME_TOLERANCE) + File.mtime(@file2).to_i.should be_close(Time.now.to_i, TIME_TOLERANCE) + end + end + + it "accepts an object that has a #to_path method" do + File.send(@method, @atime, @mtime, mock_to_path(@file1), mock_to_path(@file2)) + end + + it "accepts numeric atime and mtime arguments" do + if @time_is_float + File.send(@method, @atime.to_f, @mtime.to_f, @file1, @file2) + + File.atime(@file1).should be_close(@atime, 0.0001) + File.mtime(@file1).should be_close(@mtime, 0.0001) + File.atime(@file2).should be_close(@atime, 0.0001) + File.mtime(@file2).should be_close(@mtime, 0.0001) + else + File.send(@method, @atime.to_i, @mtime.to_i, @file1, @file2) + + File.atime(@file1).to_i.should be_close(@atime.to_i, TIME_TOLERANCE) + File.mtime(@file1).to_i.should be_close(@mtime.to_i, TIME_TOLERANCE) + File.atime(@file2).to_i.should be_close(@atime.to_i, TIME_TOLERANCE) + File.mtime(@file2).to_i.should be_close(@mtime.to_i, TIME_TOLERANCE) + end + end + + it "may set nanosecond precision" do + t = Time.utc(2007, 11, 1, 15, 25, 0, 123456.789r) + File.send(@method, t, t, @file1) + + File.atime(@file1).nsec.should.between?(0, 123500000) + File.mtime(@file1).nsec.should.between?(0, 123500000) + end + + it "returns the number of filenames in the arguments" do + File.send(@method, @atime.to_f, @mtime.to_f, @file1, @file2).should == 2 + end + + platform_is :linux do + platform_is pointer_size: 64 do + it "allows Time instances in the far future to set mtime and atime (but some filesystems limit it up to 2446-05-10 or 2038-01-19 or 2486-07-02)" do + # https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout#Inode_Timestamps + # "Therefore, timestamps should not overflow until May 2446." + # https://lwn.net/Articles/804382/ + # "On-disk timestamps hitting the y2038 limit..." + # The problem seems to be being improved, but currently it actually fails on XFS on RHEL8 + # https://rubyci.org/logs/rubyci.s3.amazonaws.com/rhel8/ruby-master/log/20201112T123004Z.fail.html.gz + # Amazon Linux 2023 returns 2486-07-02 in this example + # http://rubyci.s3.amazonaws.com/amazon2023/ruby-master/log/20230322T063004Z.fail.html.gz + time = Time.at(1<<44) + File.send(@method, time, time, @file1) + + [559444, 2486, 2446, 2038].should.include? File.atime(@file1).year + [559444, 2486, 2446, 2038].should.include? File.mtime(@file1).year + end + end + end +end diff --git a/spec/ruby/core/file/size_spec.rb b/spec/ruby/core/file/size_spec.rb new file mode 100644 index 0000000000..11d20cbacb --- /dev/null +++ b/spec/ruby/core/file/size_spec.rb @@ -0,0 +1,119 @@ +require_relative '../../spec_helper' +require_relative '../../shared/file/size' + +describe "File.size?" do + it_behaves_like :file_size, :size?, File +end + +describe "File.size?" do + it_behaves_like :file_size_to_io, :size?, File +end + +describe "File.size?" do + it_behaves_like :file_size_nil_when_missing, :size?, File +end + +describe "File.size?" do + it_behaves_like :file_size_nil_when_empty, :size?, File +end + +describe "File.size?" do + it_behaves_like :file_size_with_file_argument, :size?, File +end + +describe "File.size" do + it_behaves_like :file_size, :size, File +end + +describe "File.size" do + it_behaves_like :file_size_to_io, :size, File +end + +describe "File.size" do + it_behaves_like :file_size_raise_when_missing, :size, File +end + +describe "File.size" do + it_behaves_like :file_size_0_when_empty, :size, File +end + +describe "File.size" do + it_behaves_like :file_size_with_file_argument, :size, File +end + +describe "File#size" do + + before :each do + @name = tmp('i_exist') + touch(@name) { |f| f.write 'rubinius' } + @file = File.new @name + @file_org = @file + end + + after :each do + @file_org.close unless @file_org.closed? + rm_r @name + end + + it "is an instance method" do + @file.respond_to?(:size).should be_true + end + + it "returns the file's size as an Integer" do + @file.size.should be_an_instance_of(Integer) + end + + it "returns the file's size in bytes" do + @file.size.should == 8 + end + + platform_is_not :windows do # impossible to remove opened file on Windows + it "returns the cached size of the file if subsequently deleted" do + rm_r @file.path + @file.size.should == 8 + end + end + + it "returns the file's current size even if modified" do + File.open(@file.path,'a') {|f| f.write '!'} + @file.size.should == 9 + end + + it "raises an IOError on a closed file" do + @file.close + -> { @file.size }.should raise_error(IOError) + end + + platform_is_not :windows do + it "follows symlinks if necessary" do + ln_file = tmp('i_exist_ln') + rm_r ln_file + + begin + File.symlink(@file.path, ln_file).should == 0 + file = File.new(ln_file) + file.size.should == 8 + ensure + file.close if file && !file.closed? + rm_r ln_file + end + end + end +end + +describe "File#size for an empty file" do + before :each do + @name = tmp('empty') + touch(@name) + @file = File.new @name + end + + after :each do + @file.close unless @file.closed? + rm_r @name + end + + it "returns 0" do + @file.size.should == 0 + end +end diff --git a/spec/ruby/core/file/socket_spec.rb b/spec/ruby/core/file/socket_spec.rb new file mode 100644 index 0000000000..d3f4eb013a --- /dev/null +++ b/spec/ruby/core/file/socket_spec.rb @@ -0,0 +1,10 @@ +require_relative '../../spec_helper' +require_relative '../../shared/file/socket' + +describe "File.socket?" do + it_behaves_like :file_socket, :socket?, File + + it "returns false if file does not exist" do + File.socket?("I_am_a_bogus_file").should == false + end +end diff --git a/spec/ruby/core/file/split_spec.rb b/spec/ruby/core/file/split_spec.rb new file mode 100644 index 0000000000..7b958621b9 --- /dev/null +++ b/spec/ruby/core/file/split_spec.rb @@ -0,0 +1,64 @@ +require_relative '../../spec_helper' + +describe "File.split" do + before :each do + @backslash_ext = "C:\\foo\\bar\\baz.rb" + @backslash = "C:\\foo\\bar\\baz" + end + + it "splits the string at the last '/' when the last component does not have an extension" do + File.split("/foo/bar/baz").should == ["/foo/bar", "baz"] + File.split("C:/foo/bar/baz").should == ["C:/foo/bar", "baz"] + end + + it "splits the string at the last '/' when the last component has an extension" do + File.split("/foo/bar/baz.rb").should == ["/foo/bar", "baz.rb"] + File.split("C:/foo/bar/baz.rb").should == ["C:/foo/bar", "baz.rb"] + end + + it "splits an empty string into a '.' and an empty string" do + File.split("").should == [".", ""] + end + + platform_is_not :windows do + it "collapses multiple '/' characters and strips trailing ones" do + File.split("//foo////").should == ["/", "foo"] + end + end + + platform_is_not :windows do + it "does not split a string that contains '\\'" do + File.split(@backslash).should == [".", "C:\\foo\\bar\\baz"] + File.split(@backslash_ext).should == [".", "C:\\foo\\bar\\baz.rb"] + end + end + + platform_is :windows do + it "splits the string at the last '\\' when the last component does not have an extension" do + File.split(@backslash).should == ["C:\\foo\\bar", "baz"] + end + + it "splits the string at the last '\\' when the last component has an extension" do + File.split(@backslash_ext).should == ["C:\\foo\\bar", "baz.rb"] + end + end + + it "raises an ArgumentError when not passed a single argument" do + -> { File.split }.should raise_error(ArgumentError) + -> { File.split('string', 'another string') }.should raise_error(ArgumentError) + end + + it "raises a TypeError if the argument is not a String type" do + -> { File.split(1) }.should raise_error(TypeError) + end + + it "coerces the argument with to_str if it is not a String type" do + obj = mock("str") + obj.should_receive(:to_str).and_return("/one/two/three") + File.split(obj).should == ["/one/two", "three"] + end + + it "accepts an object that has a #to_path method" do + File.split(mock_to_path("")).should == [".", ""] + end +end diff --git a/spec/ruby/core/file/stat/atime_spec.rb b/spec/ruby/core/file/stat/atime_spec.rb new file mode 100644 index 0000000000..9f1111ced1 --- /dev/null +++ b/spec/ruby/core/file/stat/atime_spec.rb @@ -0,0 +1,18 @@ +require_relative '../../../spec_helper' + +describe "File::Stat#atime" do + before :each do + @file = tmp('i_exist') + touch(@file) { |f| f.write "rubinius" } + end + + after :each do + rm_r @file + end + + it "returns the atime of a File::Stat object" do + st = File.stat(@file) + st.atime.should be_kind_of(Time) + st.atime.should <= Time.now + end +end diff --git a/spec/ruby/core/file/stat/birthtime_spec.rb b/spec/ruby/core/file/stat/birthtime_spec.rb new file mode 100644 index 0000000000..9aa39297b2 --- /dev/null +++ b/spec/ruby/core/file/stat/birthtime_spec.rb @@ -0,0 +1,29 @@ +require_relative '../../../spec_helper' + +platform_is(:windows, :darwin, :freebsd, :netbsd, + *ruby_version_is("4.0") { :linux }, + ) do + not_implemented_messages = [ + "birthtime() function is unimplemented", # unsupported OS/version + "birthtime is unimplemented", # unsupported filesystem + ] + + describe "File::Stat#birthtime" do + before :each do + @file = tmp('i_exist') + touch(@file) { |f| f.write "rubinius" } + end + + after :each do + rm_r @file + end + + it "returns the birthtime of a File::Stat object" do + st = File.stat(@file) + st.birthtime.should be_kind_of(Time) + st.birthtime.should <= Time.now + rescue NotImplementedError => e + e.message.should.start_with?(*not_implemented_messages) + end + end +end diff --git a/spec/ruby/core/file/stat/blksize_spec.rb b/spec/ruby/core/file/stat/blksize_spec.rb new file mode 100644 index 0000000000..4d85b05e4d --- /dev/null +++ b/spec/ruby/core/file/stat/blksize_spec.rb @@ -0,0 +1,27 @@ +require_relative '../../../spec_helper' + +describe "File::Stat#blksize" do + before :each do + @file = tmp('i_exist') + touch(@file) { |f| f.write "rubinius" } + end + + after :each do + rm_r @file + end + + platform_is_not :windows do + it "returns the blksize of a File::Stat object" do + st = File.stat(@file) + st.blksize.is_a?(Integer).should == true + st.blksize.should > 0 + end + end + + platform_is :windows do + it "returns nil" do + st = File.stat(@file) + st.blksize.should == nil + end + end +end diff --git a/spec/ruby/core/file/stat/blockdev_spec.rb b/spec/ruby/core/file/stat/blockdev_spec.rb new file mode 100644 index 0000000000..f986c18125 --- /dev/null +++ b/spec/ruby/core/file/stat/blockdev_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative '../../../shared/file/blockdev' +require_relative 'fixtures/classes' + +describe "File::Stat#blockdev?" do + it_behaves_like :file_blockdev, :blockdev?, FileStat +end diff --git a/spec/ruby/core/file/stat/blocks_spec.rb b/spec/ruby/core/file/stat/blocks_spec.rb new file mode 100644 index 0000000000..f3f903d0f7 --- /dev/null +++ b/spec/ruby/core/file/stat/blocks_spec.rb @@ -0,0 +1,27 @@ +require_relative '../../../spec_helper' + +describe "File::Stat#blocks" do + before :each do + @file = tmp('i_exist') + touch(@file) { |f| f.write "rubinius" } + end + + after :each do + rm_r @file + end + + platform_is_not :windows do + it "returns a non-negative integer" do + st = File.stat(@file) + st.blocks.is_a?(Integer).should == true + st.blocks.should >= 0 + end + end + + platform_is :windows do + it "returns nil" do + st = File.stat(@file) + st.blocks.should be_nil + end + end +end diff --git a/spec/ruby/core/file/stat/chardev_spec.rb b/spec/ruby/core/file/stat/chardev_spec.rb new file mode 100644 index 0000000000..622fb2052d --- /dev/null +++ b/spec/ruby/core/file/stat/chardev_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative '../../../shared/file/chardev' +require_relative 'fixtures/classes' + +describe "File::Stat#chardev?" do + it_behaves_like :file_chardev, :chardev?, FileStat +end diff --git a/spec/ruby/core/file/stat/comparison_spec.rb b/spec/ruby/core/file/stat/comparison_spec.rb new file mode 100644 index 0000000000..faa3b6bf62 --- /dev/null +++ b/spec/ruby/core/file/stat/comparison_spec.rb @@ -0,0 +1,66 @@ +require_relative '../../../spec_helper' + +describe "File::Stat#<=>" do + before :each do + @name1 = tmp("i_exist") + @name2 = tmp("i_exist_too") + touch @name1 + touch @name2 + end + + after :each do + rm_r @name1, @name2 + end + + it "is able to compare files by the same modification times" do + now = Time.now - 1 # 1 second ago to avoid NFS cache issue + File.utime(now, now, @name1) + File.utime(now, now, @name2) + + File.open(@name1) { |file1| + File.open(@name2) { |file2| + (file1.stat <=> file2.stat).should == 0 + } + } + end + + it "is able to compare files by different modification times" do + now = Time.now + File.utime(now, now + 100, @name2) + + File.open(@name1) { |file1| + File.open(@name2) { |file2| + (file1.stat <=> file2.stat).should == -1 + } + } + + File.utime(now, now - 100, @name2) + + File.open(@name1) { |file1| + File.open(@name2) { |file2| + (file1.stat <=> file2.stat).should == 1 + } + } + end + + # TODO: Fix + it "includes Comparable and #== shows mtime equality between two File::Stat objects" do + File.open(@name1) { |file1| + File.open(@name2) { |file2| + (file1.stat == file1.stat).should == true + (file2.stat == file2.stat).should == true + } + } + + now = Time.now + File.utime(now, now + 100, @name2) + + File.open(@name1) { |file1| + File.open(@name2) { |file2| + (file1.stat == file2.stat).should == false + (file1.stat == file1.stat).should == true + (file2.stat == file2.stat).should == true + } + } + end +end diff --git a/spec/ruby/core/file/stat/ctime_spec.rb b/spec/ruby/core/file/stat/ctime_spec.rb new file mode 100644 index 0000000000..fd50487a0a --- /dev/null +++ b/spec/ruby/core/file/stat/ctime_spec.rb @@ -0,0 +1,18 @@ +require_relative '../../../spec_helper' + +describe "File::Stat#ctime" do + before :each do + @file = tmp('i_exist') + touch(@file) { |f| f.write "rubinius" } + end + + after :each do + rm_r @file + end + + it "returns the ctime of a File::Stat object" do + st = File.stat(@file) + st.ctime.should be_kind_of(Time) + st.ctime.should <= Time.now + end +end diff --git a/spec/ruby/core/file/stat/dev_major_spec.rb b/spec/ruby/core/file/stat/dev_major_spec.rb new file mode 100644 index 0000000000..4966d609e2 --- /dev/null +++ b/spec/ruby/core/file/stat/dev_major_spec.rb @@ -0,0 +1,23 @@ +require_relative '../../../spec_helper' + +describe "File::Stat#dev_major" do + before :each do + @name = tmp("file.txt") + touch(@name) + end + after :each do + rm_r @name + end + + platform_is_not :windows do + it "returns the major part of File::Stat#dev" do + File.stat(@name).dev_major.should be_kind_of(Integer) + end + end + + platform_is :windows do + it "returns nil" do + File.stat(@name).dev_major.should be_nil + end + end +end diff --git a/spec/ruby/core/file/stat/dev_minor_spec.rb b/spec/ruby/core/file/stat/dev_minor_spec.rb new file mode 100644 index 0000000000..ea79c12b99 --- /dev/null +++ b/spec/ruby/core/file/stat/dev_minor_spec.rb @@ -0,0 +1,23 @@ +require_relative '../../../spec_helper' + +describe "File::Stat#dev_minor" do + before :each do + @name = tmp("file.txt") + touch(@name) + end + after :each do + rm_r @name + end + + platform_is_not :windows do + it "returns the minor part of File::Stat#dev" do + File.stat(@name).dev_minor.should be_kind_of(Integer) + end + end + + platform_is :windows do + it "returns nil" do + File.stat(@name).dev_minor.should be_nil + end + end +end diff --git a/spec/ruby/core/file/stat/dev_spec.rb b/spec/ruby/core/file/stat/dev_spec.rb new file mode 100644 index 0000000000..e953fcaa58 --- /dev/null +++ b/spec/ruby/core/file/stat/dev_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../../spec_helper' + +describe "File::Stat#dev" do + before :each do + @name = tmp("file.txt") + touch(@name) + end + after :each do + rm_r @name + end + + it "returns the number of the device on which the file exists" do + File.stat(@name).dev.should be_kind_of(Integer) + end +end diff --git a/spec/ruby/core/file/stat/directory_spec.rb b/spec/ruby/core/file/stat/directory_spec.rb new file mode 100644 index 0000000000..c03610388b --- /dev/null +++ b/spec/ruby/core/file/stat/directory_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative '../../../shared/file/directory' +require_relative 'fixtures/classes' + +describe "File::Stat#directory?" do + it_behaves_like :file_directory, :directory?, FileStat +end diff --git a/spec/ruby/core/file/stat/executable_real_spec.rb b/spec/ruby/core/file/stat/executable_real_spec.rb new file mode 100644 index 0000000000..23bffe89c5 --- /dev/null +++ b/spec/ruby/core/file/stat/executable_real_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative '../../../shared/file/executable_real' +require_relative 'fixtures/classes' + +describe "File::Stat#executable_real?" do + it_behaves_like :file_executable_real, :executable_real?, FileStat +end diff --git a/spec/ruby/core/file/stat/executable_spec.rb b/spec/ruby/core/file/stat/executable_spec.rb new file mode 100644 index 0000000000..422975d14b --- /dev/null +++ b/spec/ruby/core/file/stat/executable_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative '../../../shared/file/executable' +require_relative 'fixtures/classes' + +describe "File::Stat#executable?" do + it_behaves_like :file_executable, :executable?, FileStat +end diff --git a/spec/ruby/core/file/stat/file_spec.rb b/spec/ruby/core/file/stat/file_spec.rb new file mode 100644 index 0000000000..d141536b4b --- /dev/null +++ b/spec/ruby/core/file/stat/file_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative '../../../shared/file/file' +require_relative 'fixtures/classes' + +describe "File::Stat#file?" do + it_behaves_like :file_file, :file?, FileStat +end diff --git a/spec/ruby/core/file/stat/fixtures/classes.rb b/spec/ruby/core/file/stat/fixtures/classes.rb new file mode 100644 index 0000000000..4fe9a2a30f --- /dev/null +++ b/spec/ruby/core/file/stat/fixtures/classes.rb @@ -0,0 +1,5 @@ +class FileStat + def self.method_missing(meth, file) + File.lstat(file).send(meth) + end +end diff --git a/spec/ruby/core/file/stat/ftype_spec.rb b/spec/ruby/core/file/stat/ftype_spec.rb new file mode 100644 index 0000000000..eb892eae5f --- /dev/null +++ b/spec/ruby/core/file/stat/ftype_spec.rb @@ -0,0 +1,64 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/file_types' + +describe "File::Stat#ftype" do + before :all do + FileSpecs.configure_types + end + + it "returns a String" do + FileSpecs.normal_file do |file| + File.lstat(file).ftype.should be_kind_of(String) + end + end + + it "returns 'file' when the file is a file" do + FileSpecs.normal_file do |file| + File.lstat(file).ftype.should == 'file' + end + end + + it "returns 'directory' when the file is a dir" do + FileSpecs.directory do |dir| + File.lstat(dir).ftype.should == 'directory' + end + end + + platform_is_not :windows do + it "returns 'characterSpecial' when the file is a char" do + FileSpecs.character_device do |char| + File.lstat(char).ftype.should == 'characterSpecial' + end + end + end + + platform_is_not :freebsd do # FreeBSD does not have block devices + with_block_device do + it "returns 'blockSpecial' when the file is a block" do + FileSpecs.block_device do |block| + File.lstat(block).ftype.should == 'blockSpecial' + end + end + end + end + + platform_is_not :windows do + it "returns 'link' when the file is a link" do + FileSpecs.symlink do |link| + File.lstat(link).ftype.should == 'link' + end + end + + it "returns fifo when the file is a fifo" do + FileSpecs.fifo do |fifo| + File.lstat(fifo).ftype.should == 'fifo' + end + end + + it "returns 'socket' when the file is a socket" do + FileSpecs.socket do |socket| + File.lstat(socket).ftype.should == 'socket' + end + end + end +end diff --git a/spec/ruby/core/file/stat/gid_spec.rb b/spec/ruby/core/file/stat/gid_spec.rb new file mode 100644 index 0000000000..3bba65bc82 --- /dev/null +++ b/spec/ruby/core/file/stat/gid_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../../spec_helper' + +describe "File::Stat#gid" do + before :each do + @file = tmp('i_exist') + touch(@file) { |f| f.write "rubinius" } + File.chown(nil, Process.gid, @file) + end + + after :each do + rm_r @file + end + + it "returns the group owner attribute of a File::Stat object" do + st = File.stat(@file) + st.gid.is_a?(Integer).should == true + st.gid.should == Process.gid + end +end diff --git a/spec/ruby/core/file/stat/grpowned_spec.rb b/spec/ruby/core/file/stat/grpowned_spec.rb new file mode 100644 index 0000000000..e7278e229b --- /dev/null +++ b/spec/ruby/core/file/stat/grpowned_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative '../../../shared/file/grpowned' +require_relative 'fixtures/classes' + +describe "File::Stat#grpowned?" do + it_behaves_like :file_grpowned, :grpowned?, FileStat +end diff --git a/spec/ruby/core/file/stat/ino_spec.rb b/spec/ruby/core/file/stat/ino_spec.rb new file mode 100644 index 0000000000..42370aecb7 --- /dev/null +++ b/spec/ruby/core/file/stat/ino_spec.rb @@ -0,0 +1,28 @@ +require_relative '../../../spec_helper' + +describe "File::Stat#ino" do + before :each do + @file = tmp('i_exist') + touch(@file) { |f| f.write "rubinius" } + end + + after :each do + rm_r @file + end + + platform_is_not :windows do + it "returns the ino of a File::Stat object" do + st = File.stat(@file) + st.ino.should be_kind_of(Integer) + st.ino.should > 0 + end + end + + platform_is :windows do + it "returns BY_HANDLE_FILE_INFORMATION.nFileIndexHigh/Low of a File::Stat object" do + st = File.stat(@file) + st.ino.should be_kind_of(Integer) + st.ino.should > 0 + end + end +end diff --git a/spec/ruby/core/file/stat/inspect_spec.rb b/spec/ruby/core/file/stat/inspect_spec.rb new file mode 100644 index 0000000000..1613b427d0 --- /dev/null +++ b/spec/ruby/core/file/stat/inspect_spec.rb @@ -0,0 +1,26 @@ +require_relative '../../../spec_helper' + +describe "File::Stat#inspect" do + + before :each do + @file = tmp('i_exist') + touch(@file) { |f| f.write "rubinius" } + end + + after :each do + rm_r @file + end + + it "produces a nicely formatted description of a File::Stat object" do + st = File.stat(@file) + expected = "#<File::Stat dev=0x#{st.dev.to_s(16)}, ino=#{st.ino}, mode=#{sprintf("%07o", st.mode)}, nlink=#{st.nlink}" + expected << ", uid=#{st.uid}, gid=#{st.gid}, rdev=0x#{st.rdev.to_s(16)}, size=#{st.size}, blksize=#{st.blksize.inspect}" + expected << ", blocks=#{st.blocks.inspect}, atime=#{st.atime.inspect}, mtime=#{st.mtime.inspect}, ctime=#{st.ctime.inspect}" + platform_is :netbsd, :freebsd, :darwin do + # Windows has File.birthtime but it's not here since already shown by ctime. + expected << ", birthtime=#{st.birthtime.inspect}" + end + expected << ">" + st.inspect.should == expected + end +end diff --git a/spec/ruby/core/file/stat/mode_spec.rb b/spec/ruby/core/file/stat/mode_spec.rb new file mode 100644 index 0000000000..c85fb85a58 --- /dev/null +++ b/spec/ruby/core/file/stat/mode_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../../spec_helper' + +describe "File::Stat#mode" do + before :each do + @file = tmp('i_exist') + touch(@file) { |f| f.write "rubinius" } + File.chmod(0644, @file) + end + + after :each do + rm_r @file + end + + it "returns the mode of a File::Stat object" do + st = File.stat(@file) + st.mode.is_a?(Integer).should == true + (st.mode & 0777).should == 0644 + end +end diff --git a/spec/ruby/core/file/stat/mtime_spec.rb b/spec/ruby/core/file/stat/mtime_spec.rb new file mode 100644 index 0000000000..08a2b83463 --- /dev/null +++ b/spec/ruby/core/file/stat/mtime_spec.rb @@ -0,0 +1,18 @@ +require_relative '../../../spec_helper' + +describe "File::Stat#mtime" do + before :each do + @file = tmp('i_exist') + touch(@file) { |f| f.write "rubinius" } + end + + after :each do + rm_r @file + end + + it "returns the mtime of a File::Stat object" do + st = File.stat(@file) + st.mtime.should be_kind_of(Time) + st.mtime.should <= Time.now + end +end diff --git a/spec/ruby/core/file/stat/new_spec.rb b/spec/ruby/core/file/stat/new_spec.rb new file mode 100644 index 0000000000..c0d9432ac8 --- /dev/null +++ b/spec/ruby/core/file/stat/new_spec.rb @@ -0,0 +1,32 @@ +require_relative '../../../spec_helper' + +describe "File::Stat#initialize" do + + before :each do + @file = tmp('i_exist') + touch(@file) { |f| f.write "rubinius" } + File.chmod(0755, @file) + end + + after :each do + rm_r @file + end + + it "raises an exception if the file doesn't exist" do + -> { + File::Stat.new(tmp("i_am_a_dummy_file_that_doesnt_exist")) + }.should raise_error(Errno::ENOENT) + end + + it "creates a File::Stat object for the given file" do + st = File::Stat.new(@file) + st.should be_kind_of(File::Stat) + st.ftype.should == 'file' + end + + it "calls #to_path on non-String arguments" do + p = mock('path') + p.should_receive(:to_path).and_return @file + File::Stat.new p + end +end diff --git a/spec/ruby/core/file/stat/nlink_spec.rb b/spec/ruby/core/file/stat/nlink_spec.rb new file mode 100644 index 0000000000..7143923cfc --- /dev/null +++ b/spec/ruby/core/file/stat/nlink_spec.rb @@ -0,0 +1,21 @@ +require_relative '../../../spec_helper' + +describe "File::Stat#nlink" do + before :each do + @file = tmp("stat_nlink") + @link = @file + ".lnk" + touch @file + end + + after :each do + rm_r @link, @file + end + + platform_is_not :windows, :android do + it "returns the number of links to a file" do + File::Stat.new(@file).nlink.should == 1 + File.link(@file, @link) + File::Stat.new(@file).nlink.should == 2 + end + end +end diff --git a/spec/ruby/core/file/stat/owned_spec.rb b/spec/ruby/core/file/stat/owned_spec.rb new file mode 100644 index 0000000000..a23ad850c5 --- /dev/null +++ b/spec/ruby/core/file/stat/owned_spec.rb @@ -0,0 +1,33 @@ +require_relative '../../../spec_helper' +require_relative '../../../shared/file/owned' +require_relative 'fixtures/classes' + +describe "File::Stat#owned?" do + it_behaves_like :file_owned, :owned?, FileStat +end + +describe "File::Stat#owned?" do + before :each do + @file = tmp("i_exist") + touch(@file) + end + + after :each do + rm_r @file + end + + it "returns true if the file is owned by the user" do + st = File.stat(@file) + st.should.owned? + end + + platform_is_not :windows, :android do + as_user do + it "returns false if the file is not owned by the user" do + system_file = '/etc/passwd' + st = File.stat(system_file) + st.should_not.owned? + end + end + end +end diff --git a/spec/ruby/core/file/stat/pipe_spec.rb b/spec/ruby/core/file/stat/pipe_spec.rb new file mode 100644 index 0000000000..692dfbf42a --- /dev/null +++ b/spec/ruby/core/file/stat/pipe_spec.rb @@ -0,0 +1,32 @@ +require_relative '../../../spec_helper' +require_relative '../../../shared/file/pipe' +require_relative 'fixtures/classes' + +describe "File::Stat#pipe?" do + it_behaves_like :file_pipe, :pipe?, FileStat +end + +describe "File::Stat#pipe?" do + it "returns false if the file is not a pipe" do + filename = tmp("i_exist") + touch(filename) + + st = File.stat(filename) + st.should_not.pipe? + + rm_r filename + end + + platform_is_not :windows do + it "returns true if the file is a pipe" do + filename = tmp("i_am_a_pipe") + File.mkfifo(filename) + + st = File.stat(filename) + st.should.pipe? + + rm_r filename + end + end + +end diff --git a/spec/ruby/core/file/stat/rdev_major_spec.rb b/spec/ruby/core/file/stat/rdev_major_spec.rb new file mode 100644 index 0000000000..e08d19c03a --- /dev/null +++ b/spec/ruby/core/file/stat/rdev_major_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../../spec_helper' + +describe "File::Stat#rdev_major" do + before :each do + @name = tmp("file.txt") + touch(@name) + end + + after :each do + rm_r @name + end + + platform_is_not :windows do + it "returns the major part of File::Stat#rdev" do + File.stat(@name).rdev_major.should be_kind_of(Integer) + end + end + + platform_is :windows do + it "returns nil" do + File.stat(@name).rdev_major.should be_nil + end + end +end diff --git a/spec/ruby/core/file/stat/rdev_minor_spec.rb b/spec/ruby/core/file/stat/rdev_minor_spec.rb new file mode 100644 index 0000000000..ace5b8a732 --- /dev/null +++ b/spec/ruby/core/file/stat/rdev_minor_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../../spec_helper' + +describe "File::Stat#rdev_minor" do + before :each do + @name = tmp("file.txt") + touch(@name) + end + + after :each do + rm_r @name + end + + platform_is_not :windows do + it "returns the minor part of File::Stat#rdev" do + File.stat(@name).rdev_minor.should be_kind_of(Integer) + end + end + + platform_is :windows do + it "returns nil" do + File.stat(@name).rdev_minor.should be_nil + end + end +end diff --git a/spec/ruby/core/file/stat/rdev_spec.rb b/spec/ruby/core/file/stat/rdev_spec.rb new file mode 100644 index 0000000000..9e1aee692d --- /dev/null +++ b/spec/ruby/core/file/stat/rdev_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../../spec_helper' + +describe "File::Stat#rdev" do + before :each do + @name = tmp("file.txt") + touch(@name) + end + after :each do + rm_r @name + end + + it "returns the number of the device this file represents which the file exists" do + File.stat(@name).rdev.should be_kind_of(Integer) + end +end diff --git a/spec/ruby/core/file/stat/readable_real_spec.rb b/spec/ruby/core/file/stat/readable_real_spec.rb new file mode 100644 index 0000000000..f138fd7b00 --- /dev/null +++ b/spec/ruby/core/file/stat/readable_real_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative '../../../shared/file/readable_real' +require_relative 'fixtures/classes' + +describe "File::Stat#readable_real?" do + it_behaves_like :file_readable_real, :readable_real?, FileStat +end diff --git a/spec/ruby/core/file/stat/readable_spec.rb b/spec/ruby/core/file/stat/readable_spec.rb new file mode 100644 index 0000000000..e99e48feed --- /dev/null +++ b/spec/ruby/core/file/stat/readable_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative '../../../shared/file/readable' +require_relative 'fixtures/classes' + +describe "File::Stat#readable?" do + it_behaves_like :file_readable, :readable?, FileStat +end diff --git a/spec/ruby/core/file/stat/setgid_spec.rb b/spec/ruby/core/file/stat/setgid_spec.rb new file mode 100644 index 0000000000..c0748ede57 --- /dev/null +++ b/spec/ruby/core/file/stat/setgid_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative '../../../shared/file/setgid' +require_relative 'fixtures/classes' + +describe "File::Stat#setgid?" do + it_behaves_like :file_setgid, :setgid?, FileStat +end diff --git a/spec/ruby/core/file/stat/setuid_spec.rb b/spec/ruby/core/file/stat/setuid_spec.rb new file mode 100644 index 0000000000..6408120fc4 --- /dev/null +++ b/spec/ruby/core/file/stat/setuid_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative '../../../shared/file/setuid' +require_relative 'fixtures/classes' + +describe "File::Stat#setuid?" do + it_behaves_like :file_setuid, :setuid?, FileStat +end diff --git a/spec/ruby/core/file/stat/size_spec.rb b/spec/ruby/core/file/stat/size_spec.rb new file mode 100644 index 0000000000..4b4f57f8c8 --- /dev/null +++ b/spec/ruby/core/file/stat/size_spec.rb @@ -0,0 +1,21 @@ +require_relative '../../../spec_helper' +require_relative '../../../shared/file/size' +require_relative 'fixtures/classes' + +describe "File::Stat.size?" do + it_behaves_like :file_size, :size?, FileStat + it_behaves_like :file_size_nil_when_empty, :size?, FileStat +end + +describe "File::Stat.size" do + it_behaves_like :file_size, :size, FileStat + it_behaves_like :file_size_0_when_empty, :size, FileStat +end + +describe "File::Stat#size" do + it "needs to be reviewed for spec completeness" +end + +describe "File::Stat#size?" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/file/stat/socket_spec.rb b/spec/ruby/core/file/stat/socket_spec.rb new file mode 100644 index 0000000000..09740be110 --- /dev/null +++ b/spec/ruby/core/file/stat/socket_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative '../../../shared/file/socket' +require_relative 'fixtures/classes' + +describe "File::Stat#socket?" do + it_behaves_like :file_socket, :socket?, FileStat +end diff --git a/spec/ruby/core/file/stat/sticky_spec.rb b/spec/ruby/core/file/stat/sticky_spec.rb new file mode 100644 index 0000000000..7083e644e9 --- /dev/null +++ b/spec/ruby/core/file/stat/sticky_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative '../../../shared/file/sticky' +require_relative 'fixtures/classes' + +describe "File::Stat#sticky?" do + it_behaves_like :file_sticky, :sticky?, FileStat +end diff --git a/spec/ruby/core/file/stat/symlink_spec.rb b/spec/ruby/core/file/stat/symlink_spec.rb new file mode 100644 index 0000000000..0def832a4c --- /dev/null +++ b/spec/ruby/core/file/stat/symlink_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative '../../../shared/file/symlink' +require_relative 'fixtures/classes' + +describe "File::Stat#symlink?" do + it_behaves_like :file_symlink, :symlink?, FileStat +end diff --git a/spec/ruby/core/file/stat/uid_spec.rb b/spec/ruby/core/file/stat/uid_spec.rb new file mode 100644 index 0000000000..b97147db21 --- /dev/null +++ b/spec/ruby/core/file/stat/uid_spec.rb @@ -0,0 +1,18 @@ +require_relative '../../../spec_helper' + +describe "File::Stat#uid" do + before :each do + @file = tmp('i_exist') + touch(@file) { |f| f.write "rubinius" } + end + + after :each do + rm_r @file + end + + it "returns the owner attribute of a File::Stat object" do + st = File.stat(@file) + st.uid.is_a?(Integer).should == true + st.uid.should == Process.uid + end +end diff --git a/spec/ruby/core/file/stat/world_readable_spec.rb b/spec/ruby/core/file/stat/world_readable_spec.rb new file mode 100644 index 0000000000..d94a02205e --- /dev/null +++ b/spec/ruby/core/file/stat/world_readable_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../../spec_helper' +require_relative '../../../shared/file/world_readable' +require_relative 'fixtures/classes' + +describe "File::Stat.world_readable?" do + it_behaves_like :file_world_readable, :world_readable?, FileStat +end + +describe "File::Stat#world_readable?" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/file/stat/world_writable_spec.rb b/spec/ruby/core/file/stat/world_writable_spec.rb new file mode 100644 index 0000000000..8100008344 --- /dev/null +++ b/spec/ruby/core/file/stat/world_writable_spec.rb @@ -0,0 +1,11 @@ +require_relative '../../../spec_helper' +require_relative '../../../shared/file/world_writable' +require_relative 'fixtures/classes' + +describe "File::Stat.world_writable?" do + it_behaves_like :file_world_writable, :world_writable?, FileStat +end + +describe "File::Stat#world_writable?" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/file/stat/writable_real_spec.rb b/spec/ruby/core/file/stat/writable_real_spec.rb new file mode 100644 index 0000000000..4c9e78eb70 --- /dev/null +++ b/spec/ruby/core/file/stat/writable_real_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative '../../../shared/file/writable_real' +require_relative 'fixtures/classes' + +describe "File::Stat#writable_real?" do + it_behaves_like :file_writable_real, :writable_real?, FileStat +end diff --git a/spec/ruby/core/file/stat/writable_spec.rb b/spec/ruby/core/file/stat/writable_spec.rb new file mode 100644 index 0000000000..551268751f --- /dev/null +++ b/spec/ruby/core/file/stat/writable_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative '../../../shared/file/writable' +require_relative 'fixtures/classes' + +describe "File::Stat#writable?" do + it_behaves_like :file_writable, :writable?, FileStat +end diff --git a/spec/ruby/core/file/stat/zero_spec.rb b/spec/ruby/core/file/stat/zero_spec.rb new file mode 100644 index 0000000000..74facac66a --- /dev/null +++ b/spec/ruby/core/file/stat/zero_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../../spec_helper' +require_relative '../../../shared/file/zero' +require_relative 'fixtures/classes' + +describe "File::Stat#zero?" do + it_behaves_like :file_zero, :zero?, FileStat +end diff --git a/spec/ruby/core/file/stat_spec.rb b/spec/ruby/core/file/stat_spec.rb new file mode 100644 index 0000000000..6365500057 --- /dev/null +++ b/spec/ruby/core/file/stat_spec.rb @@ -0,0 +1,55 @@ +require_relative '../../spec_helper' +require_relative 'shared/stat' + +describe "File.stat" do + it_behaves_like :file_stat, :stat +end + +platform_is_not :windows do + describe "File.stat" do + before :each do + @file = tmp('i_exist') + @link = tmp('i_am_a_symlink') + touch(@file) { |f| f.write "rubinius" } + end + + after :each do + rm_r @link, @file + end + + it "returns information for a file that has been deleted but is still open" do + File.open(@file) do |f| + rm_r @file + + st = f.stat + + st.should.file? + st.should_not.zero? + st.size.should == 8 + st.size?.should == 8 + st.blksize.should >= 0 + st.atime.should be_kind_of(Time) + st.ctime.should be_kind_of(Time) + st.mtime.should be_kind_of(Time) + end + end + + it "returns a File::Stat object with file properties for a symlink" do + File.symlink(@file, @link) + st = File.stat(@link) + + st.should.file? + st.should_not.symlink? + end + + it "returns an error when given missing non-ASCII path" do + missing_path = "/missingfilepath\xE3E4".b + -> { + File.stat(missing_path) + }.should raise_error(SystemCallError) { |e| + [Errno::ENOENT, Errno::EILSEQ].should include(e.class) + e.message.should include(missing_path) + } + end + end +end diff --git a/spec/ruby/core/file/sticky_spec.rb b/spec/ruby/core/file/sticky_spec.rb new file mode 100644 index 0000000000..5f7b2d93eb --- /dev/null +++ b/spec/ruby/core/file/sticky_spec.rb @@ -0,0 +1,50 @@ +require_relative '../../spec_helper' +require_relative '../../shared/file/sticky' + +describe "File.sticky?" do + it_behaves_like :file_sticky, :sticky?, File + it_behaves_like :file_sticky_missing, :sticky?, File +end + +describe "File.sticky?" do + platform_is_not :windows do + it "returns false if file does not exist" do + File.sticky?("I_am_a_bogus_file").should == false + end + + it "returns false if the file has not sticky bit set" do + filename = tmp("i_exist") + touch(filename) + + File.sticky?(filename).should == false + + rm_r filename + end + end + + platform_is :linux, :darwin do + it "returns true if the file has sticky bit set" do + filename = tmp("i_exist") + touch(filename) + system "chmod +t #{filename}" + + File.sticky?(filename).should == true + + rm_r filename + end + end + + platform_is :bsd do + # FreeBSD and NetBSD can't set sticky bit to a normal file + it "cannot set sticky bit to a normal file" do + filename = tmp("i_exist") + touch(filename) + stat = File.stat(filename) + mode = stat.mode + raise_error(Errno::EFTYPE){File.chmod(mode|01000, filename)} + File.sticky?(filename).should == false + + rm_r filename + end + end +end diff --git a/spec/ruby/core/file/symlink_spec.rb b/spec/ruby/core/file/symlink_spec.rb new file mode 100644 index 0000000000..0e8b0a5a20 --- /dev/null +++ b/spec/ruby/core/file/symlink_spec.rb @@ -0,0 +1,57 @@ +require_relative '../../spec_helper' +require_relative '../../shared/file/symlink' + +describe "File.symlink" do + before :each do + @file = tmp("file_symlink.txt") + @link = tmp("file_symlink.lnk") + + rm_r @link + touch @file + end + + after :each do + rm_r @link, @file + end + + platform_is_not :windows do + it "creates a symlink between a source and target file" do + File.symlink(@file, @link).should == 0 + File.identical?(@file, @link).should == true + end + + it "creates a symbolic link" do + File.symlink(@file, @link) + File.symlink?(@link).should == true + end + + it "accepts args that have #to_path methods" do + File.symlink(mock_to_path(@file), mock_to_path(@link)) + File.symlink?(@link).should == true + end + + it "raises an Errno::EEXIST if the target already exists" do + File.symlink(@file, @link) + -> { File.symlink(@file, @link) }.should raise_error(Errno::EEXIST) + end + + it "raises an ArgumentError if not called with two arguments" do + -> { File.symlink }.should raise_error(ArgumentError) + -> { File.symlink(@file) }.should raise_error(ArgumentError) + end + + it "raises a TypeError if not called with String types" do + -> { File.symlink(@file, nil) }.should raise_error(TypeError) + -> { File.symlink(@file, 1) }.should raise_error(TypeError) + -> { File.symlink(1, 1) }.should raise_error(TypeError) + end + end +end + +describe "File.symlink?" do + it_behaves_like :file_symlink, :symlink?, File +end + +describe "File.symlink?" do + it_behaves_like :file_symlink_nonexistent, :symlink?, File +end diff --git a/spec/ruby/core/file/to_path_spec.rb b/spec/ruby/core/file/to_path_spec.rb new file mode 100644 index 0000000000..6d168a065c --- /dev/null +++ b/spec/ruby/core/file/to_path_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/path' + +describe "File#to_path" do + it_behaves_like :file_path, :to_path +end diff --git a/spec/ruby/core/file/truncate_spec.rb b/spec/ruby/core/file/truncate_spec.rb new file mode 100644 index 0000000000..b4a2e3e577 --- /dev/null +++ b/spec/ruby/core/file/truncate_spec.rb @@ -0,0 +1,177 @@ +require_relative '../../spec_helper' + +describe "File.truncate" do + before :each do + @name = tmp("test.txt") + touch(@name) { |f| f.write("1234567890") } + end + + after :each do + rm_r @name + end + + it "truncates a file" do + File.size(@name).should == 10 + + File.truncate(@name, 5) + File.size(@name).should == 5 + + File.open(@name, "r") do |f| + f.read(99).should == "12345" + f.should.eof? + end + end + + it "truncate a file size to 0" do + File.truncate(@name, 0).should == 0 + IO.read(@name).should == "" + end + + it "truncate a file size to 5" do + File.size(@name).should == 10 + File.truncate(@name, 5) + File.size(@name).should == 5 + IO.read(@name).should == "12345" + end + + it "truncates to a larger file size than the original file" do + File.truncate(@name, 12) + File.size(@name).should == 12 + IO.read(@name).should == "1234567890\000\000" + end + + it "truncates to the same size as the original file" do + File.truncate(@name, File.size(@name)) + File.size(@name).should == 10 + IO.read(@name).should == "1234567890" + end + + it "raises an Errno::ENOENT if the file does not exist" do + # TODO: missing_file + not_existing_file = tmp("file-does-not-exist-for-sure.txt") + + # make sure it doesn't exist for real + rm_r not_existing_file + + begin + -> { File.truncate(not_existing_file, 5) }.should raise_error(Errno::ENOENT) + ensure + rm_r not_existing_file + end + end + + it "raises an ArgumentError if not passed two arguments" do + -> { File.truncate }.should raise_error(ArgumentError) + -> { File.truncate(@name) }.should raise_error(ArgumentError) + end + + platform_is_not :netbsd, :openbsd do + it "raises an Errno::EINVAL if the length argument is not valid" do + -> { File.truncate(@name, -1) }.should raise_error(Errno::EINVAL) # May fail + end + end + + it "raises a TypeError if not passed a String type for the first argument" do + -> { File.truncate(1, 1) }.should raise_error(TypeError) + end + + it "raises a TypeError if not passed an Integer type for the second argument" do + -> { File.truncate(@name, nil) }.should raise_error(TypeError) + end + + it "accepts an object that has a #to_path method" do + File.truncate(mock_to_path(@name), 0).should == 0 + end +end + + +describe "File#truncate" do + before :each do + @name = tmp("test.txt") + @file = File.open @name, 'w' + @file.write "1234567890" + @file.flush + end + + after :each do + @file.close unless @file.closed? + rm_r @name + end + + it "does not move the file write pointer to the specified byte offset" do + @file.truncate(3) + @file.write "abc" + @file.close + File.read(@name).should == "123\x00\x00\x00\x00\x00\x00\x00abc" + end + + it "does not move the file read pointer to the specified byte offset" do + File.open(@name, "r+") do |f| + f.read(1).should == "1" + f.truncate(0) + f.read(1).should == nil + end + end + + it "truncates a file" do + File.size(@name).should == 10 + + @file.truncate(5) + File.size(@name).should == 5 + File.open(@name, "r") do |f| + f.read(99).should == "12345" + f.should.eof? + end + end + + it "truncates a file size to 0" do + @file.truncate(0).should == 0 + IO.read(@name).should == "" + end + + it "truncates a file size to 5" do + File.size(@name).should == 10 + @file.truncate(5) + File.size(@name).should == 5 + IO.read(@name).should == "12345" + end + + it "truncates a file to a larger size than the original file" do + @file.truncate(12) + File.size(@name).should == 12 + IO.read(@name).should == "1234567890\000\000" + end + + it "truncates a file to the same size as the original file" do + @file.truncate(File.size(@name)) + File.size(@name).should == 10 + IO.read(@name).should == "1234567890" + end + + it "raises an ArgumentError if not passed one argument" do + -> { @file.truncate }.should raise_error(ArgumentError) + -> { @file.truncate(1) }.should_not raise_error(ArgumentError) + end + + platform_is_not :netbsd do + it "raises an Errno::EINVAL if the length argument is not valid" do + -> { @file.truncate(-1) }.should raise_error(Errno::EINVAL) # May fail + end + end + + it "raises an IOError if file is closed" do + @file.close + @file.should.closed? + -> { @file.truncate(42) }.should raise_error(IOError) + end + + it "raises an IOError if file is not opened for writing" do + File.open(@name, 'r') do |file| + -> { file.truncate(42) }.should raise_error(IOError) + end + end + + it "raises a TypeError if not passed an Integer type for the for the argument" do + -> { @file.truncate(nil) }.should raise_error(TypeError) + end +end diff --git a/spec/ruby/core/file/umask_spec.rb b/spec/ruby/core/file/umask_spec.rb new file mode 100644 index 0000000000..7f7e40abbc --- /dev/null +++ b/spec/ruby/core/file/umask_spec.rb @@ -0,0 +1,57 @@ +require_relative '../../spec_helper' + +describe "File.umask" do + before :each do + @orig_umask = File.umask + @file = tmp('test.txt') + touch @file + end + + after :each do + rm_r @file + File.umask(@orig_umask) + end + + it "returns an Integer" do + File.umask.should be_kind_of(Integer) + end + + platform_is_not :windows do + it "returns the current umask value for the process" do + File.umask(022) + File.umask(006).should == 022 + File.umask.should == 006 + end + + it "invokes to_int on non-integer argument" do + (obj = mock(022)).should_receive(:to_int).any_number_of_times.and_return(022) + File.umask(obj) + File.umask(obj).should == 022 + end + end + + platform_is :windows do + it "returns the current umask value for this process (basic)" do + File.umask.should == 0 + File.umask(022).should == 0 + File.umask(044).should == 0 + end + + # The value used here is the value of _S_IWRITE. + it "returns the current umask value for this process" do + File.umask(0000200) + File.umask.should == 0000200 + File.umask(0006) + File.umask.should == 0 + end + end + + it "raises RangeError with too large values" do + -> { File.umask(2**64) }.should raise_error(RangeError) + -> { File.umask(-2**63 - 1) }.should raise_error(RangeError) + end + + it "raises ArgumentError when more than one argument is provided" do + -> { File.umask(022, 022) }.should raise_error(ArgumentError) + end +end diff --git a/spec/ruby/core/file/unlink_spec.rb b/spec/ruby/core/file/unlink_spec.rb new file mode 100644 index 0000000000..28872d55ed --- /dev/null +++ b/spec/ruby/core/file/unlink_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/unlink' + +describe "File.unlink" do + it_behaves_like :file_unlink, :unlink +end diff --git a/spec/ruby/core/file/utime_spec.rb b/spec/ruby/core/file/utime_spec.rb new file mode 100644 index 0000000000..d87626be50 --- /dev/null +++ b/spec/ruby/core/file/utime_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/update_time' + +describe "File.utime" do + it_behaves_like :update_time, :utime +end diff --git a/spec/ruby/core/file/world_readable_spec.rb b/spec/ruby/core/file/world_readable_spec.rb new file mode 100644 index 0000000000..11b8e67d0b --- /dev/null +++ b/spec/ruby/core/file/world_readable_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require_relative '../../shared/file/world_readable' + +describe "File.world_readable?" do + it_behaves_like :file_world_readable, :world_readable?, File + + it "returns nil if the file does not exist" do + file = rand.to_s + $$.to_s + File.should_not.exist?(file) + File.world_readable?(file).should be_nil + end +end diff --git a/spec/ruby/core/file/world_writable_spec.rb b/spec/ruby/core/file/world_writable_spec.rb new file mode 100644 index 0000000000..d378cf2eb9 --- /dev/null +++ b/spec/ruby/core/file/world_writable_spec.rb @@ -0,0 +1,12 @@ +require_relative '../../spec_helper' +require_relative '../../shared/file/world_writable' + +describe "File.world_writable?" do + it_behaves_like :file_world_writable, :world_writable?, File + + it "returns nil if the file does not exist" do + file = rand.to_s + $$.to_s + File.should_not.exist?(file) + File.world_writable?(file).should be_nil + end +end diff --git a/spec/ruby/core/file/writable_real_spec.rb b/spec/ruby/core/file/writable_real_spec.rb new file mode 100644 index 0000000000..bea4c4c262 --- /dev/null +++ b/spec/ruby/core/file/writable_real_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative '../../shared/file/writable_real' + +describe "File.writable_real?" do + it_behaves_like :file_writable_real, :writable_real?, File + it_behaves_like :file_writable_real_missing, :writable_real?, File +end diff --git a/spec/ruby/core/file/writable_spec.rb b/spec/ruby/core/file/writable_spec.rb new file mode 100644 index 0000000000..519837b0d1 --- /dev/null +++ b/spec/ruby/core/file/writable_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative '../../shared/file/writable' + +describe "File.writable?" do + it_behaves_like :file_writable, :writable?, File + it_behaves_like :file_writable_missing, :writable?, File +end diff --git a/spec/ruby/core/file/zero_spec.rb b/spec/ruby/core/file/zero_spec.rb new file mode 100644 index 0000000000..01c7505ef2 --- /dev/null +++ b/spec/ruby/core/file/zero_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative '../../shared/file/zero' + +describe "File.zero?" do + it_behaves_like :file_zero, :zero?, File + it_behaves_like :file_zero_missing, :zero?, File +end |
