diff options
Diffstat (limited to 'spec/ruby/core/dir')
43 files changed, 3015 insertions, 0 deletions
diff --git a/spec/ruby/core/dir/chdir_spec.rb b/spec/ruby/core/dir/chdir_spec.rb new file mode 100644 index 0000000000..015386a902 --- /dev/null +++ b/spec/ruby/core/dir/chdir_spec.rb @@ -0,0 +1,220 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' + +describe "Dir.chdir" do + before :all do + DirSpecs.create_mock_dirs + end + + after :all do + DirSpecs.delete_mock_dirs + end + + before :each do + @original = Dir.pwd + end + + after :each do + Dir.chdir(@original) + end + + it "defaults to $HOME with no arguments" do + skip "$HOME not valid directory" unless ENV['HOME'] && File.directory?(ENV['HOME']) + + Dir.chdir + current_dir = Dir.pwd + + Dir.chdir(ENV['HOME']) + home = Dir.pwd + current_dir.should == home + end + + it "changes to the specified directory" do + Dir.chdir DirSpecs.mock_dir + Dir.pwd.should == DirSpecs.mock_dir + end + + it "returns 0 when successfully changing directory" do + Dir.chdir(@original).should == 0 + end + + it "calls #to_str on the argument if it's not a String" do + obj = mock('path') + obj.should_receive(:to_str).and_return(Dir.pwd) + Dir.chdir(obj) + end + + it "calls #to_str on the argument if it's not a String and a block is given" do + obj = mock('path') + obj.should_receive(:to_str).and_return(Dir.pwd) + Dir.chdir(obj) { } + end + + it "calls #to_path on the argument if it's not a String" do + obj = mock('path') + obj.should_receive(:to_path).and_return(Dir.pwd) + Dir.chdir(obj) + end + + it "prefers #to_path over #to_str" do + obj = Class.new do + def to_path; Dir.pwd; end + def to_str; DirSpecs.mock_dir; end + end + Dir.chdir(obj.new) + Dir.pwd.should == @original + end + + it "returns the value of the block when a block is given" do + Dir.chdir(@original) { :block_value }.should == :block_value + end + + it "defaults to the home directory when given a block but no argument" do + skip "$HOME not valid directory" unless ENV['HOME'] && File.directory?(ENV['HOME']) + + # Windows will return a path with forward slashes for ENV["HOME"] so we have + # to compare the route representations returned by Dir.chdir. + current_dir = "" + Dir.chdir { current_dir = Dir.pwd } + + Dir.chdir(ENV['HOME']) + home = Dir.pwd + current_dir.should == home + end + + it "changes to the specified directory for the duration of the block" do + ar = Dir.chdir(DirSpecs.mock_dir) { |dir| [dir, Dir.pwd] } + ar.should == [DirSpecs.mock_dir, DirSpecs.mock_dir] + + Dir.pwd.should == @original + end + + it "raises an Errno::ENOENT if the directory does not exist" do + -> { Dir.chdir DirSpecs.nonexistent }.should raise_error(Errno::ENOENT) + -> { Dir.chdir(DirSpecs.nonexistent) { } }.should raise_error(Errno::ENOENT) + end + + it "raises an Errno::ENOENT if the original directory no longer exists" do + dir1 = tmp('testdir1') + dir2 = tmp('testdir2') + Dir.should_not.exist?(dir1) + Dir.should_not.exist?(dir2) + Dir.mkdir dir1 + Dir.mkdir dir2 + begin + -> { + Dir.chdir dir1 do + Dir.chdir(dir2) { Dir.unlink dir1 } + end + }.should raise_error(Errno::ENOENT) + ensure + Dir.unlink dir1 if Dir.exist?(dir1) + Dir.unlink dir2 if Dir.exist?(dir2) + end + end + + it "always returns to the original directory when given a block" do + begin + Dir.chdir(DirSpecs.mock_dir) do + raise StandardError, "something bad happened" + end + rescue StandardError + end + + Dir.pwd.should == @original + end +end + +ruby_version_is '3.3' do + describe "Dir#chdir" do + before :all do + DirSpecs.create_mock_dirs + end + + after :all do + DirSpecs.delete_mock_dirs + end + + before :each do + @original = Dir.pwd + end + + after :each do + Dir.chdir(@original) + end + + it "changes the current working directory to self" do + dir = Dir.new(DirSpecs.mock_dir) + dir.chdir + Dir.pwd.should == DirSpecs.mock_dir + ensure + dir.close + end + + it "changes the current working directory to self for duration of the block when a block is given" do + dir = Dir.new(DirSpecs.mock_dir) + pwd_in_block = nil + + dir.chdir { pwd_in_block = Dir.pwd } + + pwd_in_block.should == DirSpecs.mock_dir + Dir.pwd.should == @original + ensure + dir.close + end + + it "returns 0 when successfully changing directory" do + dir = Dir.new(DirSpecs.mock_dir) + dir.chdir.should == 0 + ensure + dir.close + end + + it "returns the value of the block when a block is given" do + dir = Dir.new(DirSpecs.mock_dir) + dir.chdir { :block_value }.should == :block_value + ensure + dir.close + end + + platform_is_not :windows do + it "does not raise an Errno::ENOENT if the original directory no longer exists" do + dir_name1 = tmp('testdir1') + dir_name2 = tmp('testdir2') + Dir.should_not.exist?(dir_name1) + Dir.should_not.exist?(dir_name2) + Dir.mkdir dir_name1 + Dir.mkdir dir_name2 + + dir2 = Dir.new(dir_name2) + + begin + Dir.chdir(dir_name1) do + dir2.chdir { Dir.unlink dir_name1 } + end + Dir.pwd.should == @original + ensure + Dir.unlink dir_name1 if Dir.exist?(dir_name1) + Dir.unlink dir_name2 if Dir.exist?(dir_name2) + end + ensure + dir2.close + end + end + + it "always returns to the original directory when given a block" do + dir = Dir.new(DirSpecs.mock_dir) + + begin + dir.chdir do + raise StandardError, "something bad happened" + end + rescue StandardError + end + + Dir.pwd.should == @original + ensure + dir.close + end + end +end diff --git a/spec/ruby/core/dir/children_spec.rb b/spec/ruby/core/dir/children_spec.rb new file mode 100644 index 0000000000..0ad3df4669 --- /dev/null +++ b/spec/ruby/core/dir/children_spec.rb @@ -0,0 +1,147 @@ +# encoding: utf-8 + +require_relative '../../spec_helper' +require_relative 'fixtures/common' + +describe "Dir.children" do + before :all do + DirSpecs.create_mock_dirs + end + + before :each do + @internal = Encoding.default_internal + end + + after :all do + DirSpecs.delete_mock_dirs + end + + after :each do + Encoding.default_internal = @internal + end + + it "returns an Array of filenames in an existing directory including dotfiles" do + a = Dir.children(DirSpecs.mock_dir).sort + + a.should == DirSpecs.expected_paths - %w[. ..] + + a = Dir.children("#{DirSpecs.mock_dir}/deeply/nested").sort + a.should == %w|.dotfile.ext directory| + end + + it "calls #to_path on non-String arguments" do + p = mock('path') + p.should_receive(:to_path).and_return(DirSpecs.mock_dir) + Dir.children(p) + end + + it "accepts an options Hash" do + a = Dir.children("#{DirSpecs.mock_dir}/deeply/nested", encoding: "utf-8").sort + a.should == %w|.dotfile.ext directory| + end + + it "returns children encoded with the filesystem encoding by default" do + # This spec depends on the locale not being US-ASCII because if it is, the + # children that are not ascii_only? will be BINARY encoded. + children = Dir.children(File.join(DirSpecs.mock_dir, 'special')).sort + encoding = Encoding.find("filesystem") + encoding = Encoding::BINARY if encoding == Encoding::US_ASCII + platform_is_not :windows do + children.should include("こんにちは.txt".dup.force_encoding(encoding)) + end + children.first.encoding.should equal(Encoding.find("filesystem")) + end + + it "returns children encoded with the specified encoding" do + dir = File.join(DirSpecs.mock_dir, 'special') + children = Dir.children(dir, encoding: "euc-jp").sort + children.first.encoding.should equal(Encoding::EUC_JP) + end + + it "returns children transcoded to the default internal encoding" do + Encoding.default_internal = Encoding::EUC_KR + children = Dir.children(File.join(DirSpecs.mock_dir, 'special')).sort + children.first.encoding.should equal(Encoding::EUC_KR) + end + + it "raises a SystemCallError if called with a nonexistent directory" do + -> { Dir.children DirSpecs.nonexistent }.should raise_error(SystemCallError) + end +end + +describe "Dir#children" do + before :all do + DirSpecs.create_mock_dirs + end + + before :each do + @internal = Encoding.default_internal + end + + after :all do + DirSpecs.delete_mock_dirs + end + + after :each do + Encoding.default_internal = @internal + @dir.close if @dir + end + + it "returns an Array of filenames in an existing directory including dotfiles" do + @dir = Dir.new(DirSpecs.mock_dir) + a = @dir.children.sort + @dir.close + + a.should == DirSpecs.expected_paths - %w[. ..] + + @dir = Dir.new("#{DirSpecs.mock_dir}/deeply/nested") + a = @dir.children.sort + a.should == %w|.dotfile.ext directory| + end + + it "accepts an encoding keyword for the encoding of the entries" do + @dir = Dir.new("#{DirSpecs.mock_dir}/deeply/nested", encoding: "utf-8") + dirs = @dir.to_a.sort + dirs.each { |d| d.encoding.should == Encoding::UTF_8 } + end + + it "returns children encoded with the filesystem encoding by default" do + # This spec depends on the locale not being US-ASCII because if it is, the + # children that are not ascii_only? will be BINARY encoded. + @dir = Dir.new(File.join(DirSpecs.mock_dir, 'special')) + children = @dir.children.sort + encoding = Encoding.find("filesystem") + encoding = Encoding::BINARY if encoding == Encoding::US_ASCII + platform_is_not :windows do + children.should include("こんにちは.txt".dup.force_encoding(encoding)) + end + children.first.encoding.should equal(Encoding.find("filesystem")) + end + + it "returns children encoded with the specified encoding" do + path = File.join(DirSpecs.mock_dir, 'special') + @dir = Dir.new(path, encoding: "euc-jp") + children = @dir.children.sort + children.first.encoding.should equal(Encoding::EUC_JP) + end + + it "returns children transcoded to the default internal encoding" do + Encoding.default_internal = Encoding::EUC_KR + @dir = Dir.new(File.join(DirSpecs.mock_dir, 'special')) + children = @dir.children.sort + children.first.encoding.should equal(Encoding::EUC_KR) + end + + it "returns the same result when called repeatedly" do + @dir = Dir.open DirSpecs.mock_dir + + a = [] + @dir.each {|dir| a << dir} + + b = [] + @dir.each {|dir| b << dir} + + a.sort.should == b.sort + a.sort.should == DirSpecs.expected_paths + end +end diff --git a/spec/ruby/core/dir/chroot_spec.rb b/spec/ruby/core/dir/chroot_spec.rb new file mode 100644 index 0000000000..a5ca8943fc --- /dev/null +++ b/spec/ruby/core/dir/chroot_spec.rb @@ -0,0 +1,47 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' +require_relative 'shared/chroot' + +platform_is_not :windows do + as_superuser do + describe "Dir.chroot as root" do + it_behaves_like :dir_chroot_as_root, :chroot + end + end + + platform_is_not :cygwin, :android do + as_user do + describe "Dir.chroot as regular user" do + before :all do + DirSpecs.create_mock_dirs + end + + after :all do + DirSpecs.delete_mock_dirs + end + + it "raises an Errno::EPERM exception if the directory exists" do + -> { Dir.chroot('.') }.should raise_error(Errno::EPERM) + end + + it "raises a SystemCallError if the directory doesn't exist" do + -> { Dir.chroot('xgwhwhsjai2222jg') }.should raise_error(SystemCallError) + end + + it "calls #to_path on non-String argument" do + p = mock('path') + p.should_receive(:to_path).and_return('.') + -> { Dir.chroot(p) }.should raise_error(Errno::EPERM) + end + end + end + end + + platform_is :cygwin do + as_user do + describe "Dir.chroot as regular user" do + it_behaves_like :dir_chroot_as_root, :chroot + end + end + end +end diff --git a/spec/ruby/core/dir/close_spec.rb b/spec/ruby/core/dir/close_spec.rb new file mode 100644 index 0000000000..f7cce318b8 --- /dev/null +++ b/spec/ruby/core/dir/close_spec.rb @@ -0,0 +1,53 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' +describe "Dir#close" do + before :all do + DirSpecs.create_mock_dirs + end + + after :all do + DirSpecs.delete_mock_dirs + end + + it "does not raise an IOError even if the Dir instance is closed" do + dir = Dir.open DirSpecs.mock_dir + dir.close.should == nil + dir.close.should == nil + + platform_is_not :windows do + -> { dir.fileno }.should raise_error(IOError, /closed directory/) + end + end + + it "returns nil" do + dir = Dir.open DirSpecs.mock_dir + dir.close.should == nil + end + + ruby_version_is '3.3'...'3.4' do + platform_is_not :windows do + it "does not raise an error even if the file descriptor is closed with another Dir instance" do + dir = Dir.open DirSpecs.mock_dir + dir_new = Dir.for_fd(dir.fileno) + + dir.close + dir_new.close + + -> { dir.fileno }.should raise_error(IOError, /closed directory/) + -> { dir_new.fileno }.should raise_error(IOError, /closed directory/) + end + end + end + + ruby_version_is '3.4' do + platform_is_not :windows do + it "raises an error if the file descriptor is closed with another Dir instance" do + dir = Dir.open DirSpecs.mock_dir + dir_new = Dir.for_fd(dir.fileno) + dir.close + + -> { dir_new.close }.should raise_error(Errno::EBADF, 'Bad file descriptor - closedir') + end + end + end +end diff --git a/spec/ruby/core/dir/delete_spec.rb b/spec/ruby/core/dir/delete_spec.rb new file mode 100644 index 0000000000..a0020788ca --- /dev/null +++ b/spec/ruby/core/dir/delete_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' +require_relative 'shared/delete' + +describe "Dir.delete" do + before :all do + DirSpecs.create_mock_dirs + end + + after :all do + DirSpecs.delete_mock_dirs + end + + it_behaves_like :dir_delete, :delete +end diff --git a/spec/ruby/core/dir/dir_spec.rb b/spec/ruby/core/dir/dir_spec.rb new file mode 100644 index 0000000000..7d55ea26d4 --- /dev/null +++ b/spec/ruby/core/dir/dir_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' + +describe "Dir" do + it "includes Enumerable" do + Dir.include?(Enumerable).should == true + end +end diff --git a/spec/ruby/core/dir/each_child_spec.rb b/spec/ruby/core/dir/each_child_spec.rb new file mode 100644 index 0000000000..7194273b95 --- /dev/null +++ b/spec/ruby/core/dir/each_child_spec.rb @@ -0,0 +1,119 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' + +describe "Dir.each_child" do + before :all do + DirSpecs.create_mock_dirs + end + + after :all do + DirSpecs.delete_mock_dirs + end + + it "accepts an encoding keyword for the encoding of the entries" do + dirs = Dir.each_child("#{DirSpecs.mock_dir}/deeply/nested", encoding: "utf-8").to_a.sort + dirs.each {|dir| dir.encoding.should == Encoding::UTF_8} + end + + it "yields all names in an existing directory to the provided block" do + a, b = [], [] + + Dir.each_child(DirSpecs.mock_dir) {|f| a << f} + Dir.each_child("#{DirSpecs.mock_dir}/deeply/nested") {|f| b << f} + + a.sort.should == DirSpecs.expected_paths - %w[. ..] + b.sort.should == %w|.dotfile.ext directory| + end + + it "returns nil when successful" do + Dir.each_child(DirSpecs.mock_dir) {|f| f}.should == nil + end + + it "calls #to_path on non-String arguments" do + p = mock('path') + p.should_receive(:to_path).and_return(DirSpecs.mock_dir) + Dir.each_child(p).to_a + end + + it "raises a SystemCallError if passed a nonexistent directory" do + -> { Dir.each_child(DirSpecs.nonexistent) {} }.should raise_error(SystemCallError) + end + + describe "when no block is given" do + it "returns an Enumerator" do + Dir.each_child(DirSpecs.mock_dir).should be_an_instance_of(Enumerator) + Dir.each_child(DirSpecs.mock_dir).to_a.sort.should == DirSpecs.expected_paths - %w[. ..] + end + + describe "returned Enumerator" do + describe "size" do + it "should return nil" do + Dir.each_child(DirSpecs.mock_dir).size.should == nil + end + end + end + end +end + +describe "Dir#each_child" do + before :all do + DirSpecs.create_mock_dirs + end + + after :all do + DirSpecs.delete_mock_dirs + end + + after :each do + @dir.close if @dir + end + + it "yields all names in an existing directory to the provided block" do + a, b = [], [] + @dir = Dir.new(DirSpecs.mock_dir) + @dir2 = Dir.new("#{DirSpecs.mock_dir}/deeply/nested") + + @dir.each_child { |f| a << f } + @dir2.each_child { |f| b << f } + @dir2.close + + a.sort.should == DirSpecs.expected_paths - %w|. ..| + b.sort.should == %w|.dotfile.ext directory| + end + + it "returns self when successful" do + @dir = Dir.new(DirSpecs.mock_dir) + @dir.each_child { |f| f }.should == @dir + end + + it "returns the same result when called repeatedly" do + @dir = Dir.open DirSpecs.mock_dir + + a = [] + @dir.each {|dir| a << dir} + + b = [] + @dir.each {|dir| b << dir} + + a.sort.should == b.sort + a.sort.should == DirSpecs.expected_paths + end + + describe "when no block is given" do + it "returns an Enumerator" do + @dir = Dir.new(DirSpecs.mock_dir) + + @dir.each_child.should be_an_instance_of(Enumerator) + @dir.each_child.to_a.sort.should == DirSpecs.expected_paths - %w|. ..| + end + + describe "returned Enumerator" do + describe "size" do + it "should return nil" do + @dir = Dir.new(DirSpecs.mock_dir) + @dir.each_child.size.should == nil + end + end + end + end +end diff --git a/spec/ruby/core/dir/each_spec.rb b/spec/ruby/core/dir/each_spec.rb new file mode 100644 index 0000000000..7674663d82 --- /dev/null +++ b/spec/ruby/core/dir/each_spec.rb @@ -0,0 +1,75 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' +require_relative 'shared/closed' + +describe "Dir#each" do + before :all do + DirSpecs.create_mock_dirs + end + + after :all do + DirSpecs.delete_mock_dirs + end + + before :each do + @dir = Dir.open DirSpecs.mock_dir + end + + after :each do + @dir.close + end + + it "yields each directory entry in succession" do + a = [] + @dir.each {|dir| a << dir} + + a.sort.should == DirSpecs.expected_paths + end + + it "returns the directory which remains open" do + # an FS does not necessarily impose order + ls = Dir.entries(DirSpecs.mock_dir) + @dir.each {}.should == @dir + @dir.read.should == nil + @dir.rewind + ls.should include(@dir.read) + end + + it "returns the same result when called repeatedly" do + a = [] + @dir.each {|dir| a << dir} + + b = [] + @dir.each {|dir| b << dir} + + a.sort.should == b.sort + a.sort.should == DirSpecs.expected_paths + end + + describe "when no block is given" do + it "returns an Enumerator" do + @dir.each.should be_an_instance_of(Enumerator) + @dir.each.to_a.sort.should == DirSpecs.expected_paths + end + + describe "returned Enumerator" do + describe "size" do + it "should return nil" do + @dir.each.size.should == nil + end + end + end + end +end + +describe "Dir#each" do + before :all do + DirSpecs.create_mock_dirs + end + + after :all do + DirSpecs.delete_mock_dirs + end + + it_behaves_like :dir_closed, :each +end diff --git a/spec/ruby/core/dir/element_reference_spec.rb b/spec/ruby/core/dir/element_reference_spec.rb new file mode 100644 index 0000000000..092114bed4 --- /dev/null +++ b/spec/ruby/core/dir/element_reference_spec.rb @@ -0,0 +1,33 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' +require_relative 'shared/glob' + +describe "Dir.[]" do + it_behaves_like :dir_glob, :[] +end + +describe "Dir.[]" do + it_behaves_like :dir_glob_recursive, :[] +end + +describe "Dir.[]" do + before :all do + DirSpecs.create_mock_dirs + @cwd = Dir.pwd + Dir.chdir DirSpecs.mock_dir + end + + after :all do + Dir.chdir @cwd + DirSpecs.delete_mock_dirs + end + + it "calls #to_path to convert multiple patterns" do + pat1 = mock('file_one.ext') + pat1.should_receive(:to_path).and_return('file_one.ext') + pat2 = mock('file_two.ext') + pat2.should_receive(:to_path).and_return('file_two.ext') + + Dir[pat1, pat2].should == %w[file_one.ext file_two.ext] + end +end diff --git a/spec/ruby/core/dir/empty_spec.rb b/spec/ruby/core/dir/empty_spec.rb new file mode 100644 index 0000000000..8cc8757798 --- /dev/null +++ b/spec/ruby/core/dir/empty_spec.rb @@ -0,0 +1,31 @@ +require_relative '../../spec_helper' + +describe "Dir.empty?" do + before :all do + @empty_dir = tmp("empty_dir") + mkdir_p @empty_dir + end + + after :all do + rm_r @empty_dir + end + + it "returns true for empty directories" do + result = Dir.empty? @empty_dir + result.should be_true + end + + it "returns false for non-empty directories" do + result = Dir.empty? __dir__ + result.should be_false + end + + it "returns false for a non-directory" do + result = Dir.empty? __FILE__ + result.should be_false + end + + it "raises ENOENT for nonexistent directories" do + -> { Dir.empty? tmp("nonexistent") }.should raise_error(Errno::ENOENT) + end +end diff --git a/spec/ruby/core/dir/entries_spec.rb b/spec/ruby/core/dir/entries_spec.rb new file mode 100644 index 0000000000..7462542acf --- /dev/null +++ b/spec/ruby/core/dir/entries_spec.rb @@ -0,0 +1,70 @@ +# encoding: utf-8 + +require_relative '../../spec_helper' +require_relative 'fixtures/common' + +describe "Dir.entries" do + before :all do + DirSpecs.create_mock_dirs + end + + before :each do + @internal = Encoding.default_internal + end + + after :all do + DirSpecs.delete_mock_dirs + end + + after :each do + Encoding.default_internal = @internal + end + + it "returns an Array of filenames in an existing directory including dotfiles" do + a = Dir.entries(DirSpecs.mock_dir).sort + + a.should == DirSpecs.expected_paths + + a = Dir.entries("#{DirSpecs.mock_dir}/deeply/nested").sort + a.should == %w|. .. .dotfile.ext directory| + end + + it "calls #to_path on non-String arguments" do + p = mock('path') + p.should_receive(:to_path).and_return(DirSpecs.mock_dir) + Dir.entries(p) + end + + it "accepts an encoding keyword for the encoding of the entries" do + dirs = Dir.entries("#{DirSpecs.mock_dir}/deeply/nested", encoding: "utf-8").to_a.sort + dirs.each {|dir| dir.encoding.should == Encoding::UTF_8} + end + + it "returns entries encoded with the filesystem encoding by default" do + # This spec depends on the locale not being US-ASCII because if it is, the + # entries that are not ascii_only? will be BINARY encoded. + entries = Dir.entries(File.join(DirSpecs.mock_dir, 'special')).sort + encoding = Encoding.find("filesystem") + encoding = Encoding::BINARY if encoding == Encoding::US_ASCII + platform_is_not :windows do + entries.should include("こんにちは.txt".dup.force_encoding(encoding)) + end + entries.first.encoding.should equal(Encoding.find("filesystem")) + end + + it "returns entries encoded with the specified encoding" do + dir = File.join(DirSpecs.mock_dir, 'special') + entries = Dir.entries(dir, encoding: "euc-jp").sort + entries.first.encoding.should equal(Encoding::EUC_JP) + end + + it "returns entries transcoded to the default internal encoding" do + Encoding.default_internal = Encoding::EUC_KR + entries = Dir.entries(File.join(DirSpecs.mock_dir, 'special')).sort + entries.first.encoding.should equal(Encoding::EUC_KR) + end + + it "raises a SystemCallError if called with a nonexistent directory" do + -> { Dir.entries DirSpecs.nonexistent }.should raise_error(SystemCallError) + end +end diff --git a/spec/ruby/core/dir/exist_spec.rb b/spec/ruby/core/dir/exist_spec.rb new file mode 100644 index 0000000000..0b8e521894 --- /dev/null +++ b/spec/ruby/core/dir/exist_spec.rb @@ -0,0 +1,21 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' +require_relative 'shared/exist' + +describe "Dir.exist?" do + before :all do + DirSpecs.create_mock_dirs + end + + after :all do + DirSpecs.delete_mock_dirs + end + + it_behaves_like :dir_exist, :exist? +end + +describe "Dir.exists?" do + it "has been removed" do + Dir.should_not.respond_to?(:exists?) + end +end diff --git a/spec/ruby/core/dir/fchdir_spec.rb b/spec/ruby/core/dir/fchdir_spec.rb new file mode 100644 index 0000000000..52600a95f2 --- /dev/null +++ b/spec/ruby/core/dir/fchdir_spec.rb @@ -0,0 +1,73 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' + +ruby_version_is '3.3' do + platform_is_not :windows do + describe "Dir.fchdir" do + before :all do + DirSpecs.create_mock_dirs + end + + after :all do + DirSpecs.delete_mock_dirs + end + + before :each do + @original = Dir.pwd + end + + after :each do + Dir.chdir(@original) + end + + it "changes the current working directory to the directory specified by the integer file descriptor" do + dir = Dir.new(DirSpecs.mock_dir) + Dir.fchdir dir.fileno + Dir.pwd.should == DirSpecs.mock_dir + ensure + dir.close + end + + it "returns 0 when successfully changing directory" do + dir = Dir.new(DirSpecs.mock_dir) + Dir.fchdir(dir.fileno).should == 0 + ensure + dir.close + end + + it "returns the value of the block when a block is given" do + dir = Dir.new(DirSpecs.mock_dir) + Dir.fchdir(dir.fileno) { :block_value }.should == :block_value + ensure + dir.close + end + + it "changes to the specified directory for the duration of the block" do + dir = Dir.new(DirSpecs.mock_dir) + Dir.fchdir(dir.fileno) { Dir.pwd }.should == DirSpecs.mock_dir + Dir.pwd.should == @original + ensure + dir.close + end + + it "raises a SystemCallError if the file descriptor given is not valid" do + -> { Dir.fchdir(-1) }.should raise_error(SystemCallError, "Bad file descriptor - fchdir") + -> { Dir.fchdir(-1) { } }.should raise_error(SystemCallError, "Bad file descriptor - fchdir") + end + + it "raises a SystemCallError if the file descriptor given is not for a directory" do + -> { Dir.fchdir $stdout.fileno }.should raise_error(SystemCallError, /(Not a directory|Invalid argument) - fchdir/) + -> { Dir.fchdir($stdout.fileno) { } }.should raise_error(SystemCallError, /(Not a directory|Invalid argument) - fchdir/) + end + end + end + + platform_is :windows do + describe "Dir.fchdir" do + it "raises NotImplementedError" do + -> { Dir.fchdir 1 }.should raise_error(NotImplementedError) + -> { Dir.fchdir(1) { } }.should raise_error(NotImplementedError) + end + end + end +end diff --git a/spec/ruby/core/dir/fileno_spec.rb b/spec/ruby/core/dir/fileno_spec.rb new file mode 100644 index 0000000000..bb84ef5378 --- /dev/null +++ b/spec/ruby/core/dir/fileno_spec.rb @@ -0,0 +1,37 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' + +has_dir_fileno = begin + dir = Dir.new('.') + dir.fileno + true +rescue NotImplementedError + false +rescue Exception + true +ensure + dir.close +end + +describe "Dir#fileno" do + before :each do + @name = tmp("fileno") + mkdir_p @name + @dir = Dir.new(@name) + end + + after :each do + @dir.close + rm_r @name + end + + if has_dir_fileno + it "returns the file descriptor of the dir" do + @dir.fileno.should be_kind_of(Integer) + end + else + it "raises an error when not implemented on the platform" do + -> { @dir.fileno }.should raise_error(NotImplementedError) + end + end +end diff --git a/spec/ruby/core/dir/fixtures/common.rb b/spec/ruby/core/dir/fixtures/common.rb new file mode 100644 index 0000000000..848656c9b9 --- /dev/null +++ b/spec/ruby/core/dir/fixtures/common.rb @@ -0,0 +1,198 @@ +# encoding: utf-8 + +module DirSpecs + def self.mock_dir(dirs = ['dir_specs_mock']) + @mock_dir ||= tmp("") + File.join @mock_dir, dirs + end + + def self.nonexistent + name = File.join mock_dir, "nonexistent00" + name = name.next while File.exist? name + name + end + + # TODO: make these relative to the mock_dir + def self.clear_dirs + [ 'nonexisting', + 'default_perms', + 'reduced', + 'always_returns_0', + '???', + [0xe9].pack('U') + ].each do |dir| + begin + Dir.rmdir mock_dir(dir) + rescue + end + end + end + + # The names of the fixture directories and files used by + # various Dir specs. + def self.mock_dir_files + unless @mock_dir_files + @mock_dir_files = %w[ + .dotfile + .dotsubdir/.dotfile + .dotsubdir/nondotfile + nested/.dotsubir/.dotfile + nested/.dotsubir/nondotfile + + deeply/.dotfile + deeply/nested/.dotfile.ext + deeply/nested/directory/structure/.ext + deeply/nested/directory/structure/bar + deeply/nested/directory/structure/baz + deeply/nested/directory/structure/file_one + deeply/nested/directory/structure/file_one.ext + deeply/nested/directory/structure/foo + deeply/nondotfile + + file_one.ext + file_two.ext + + dir_filename_ordering + dir/filename_ordering + + nondotfile + + subdir_one/.dotfile + subdir_one/nondotfile + subdir_two/nondotfile + subdir_two/nondotfile.ext + + brace/a + brace/a.js + brace/a.erb + brace/a.js.rjs + brace/a.html.erb + + special/+ + + special/^ + special/$ + + special/( + special/) + special/[ + special/] + special/{ + special/} + + special/test{1}/file[1] + special/{}/special + special/test\ +()[]{}/hello_world.erb + ] + + platform_is_not :windows do + @mock_dir_files += %w[ + special/* + special/? + + special/| + + special/こんにちは.txt + special/\a + ] + @mock_dir_files << "special/_\u{1f60e}.erb" + end + end + + @mock_dir_files + end + + def self.mock_dir_links + unless @mock_dir_links + @mock_dir_links = [] + platform_is_not :windows do + @mock_dir_links += [ + ['special/ln', 'subdir_one'] + ] + end + end + @mock_dir_links + end + + def self.create_mock_dirs + mock_dir_files.each do |name| + file = File.join mock_dir, name + mkdir_p File.dirname(file) + touch file + end + mock_dir_links.each do |link, target| + full_link = File.join mock_dir, link + full_target = File.join mock_dir, target + + File.symlink full_target, full_link + end + end + + def self.delete_mock_dirs + begin + rm_r mock_dir + rescue Errno::ENOTEMPTY => e + puts Dir["#{mock_dir}/**/*"] + raise e + end + end + + def self.mock_rmdir(*dirs) + mock_dir ['rmdir_dirs'].concat(dirs) + end + + def self.rmdir_dirs(create = true) + dirs = %w[ + empty + nonempty + nonempty/child + noperm + noperm/child + ] + + base_dir = mock_dir ['rmdir_dirs'] + + dirs.reverse_each do |d| + dir = File.join base_dir, d + if File.exist? dir + File.chmod 0777, dir + rm_r dir + end + end + rm_r base_dir + + if create + dirs.each do |d| + dir = File.join base_dir, d + unless File.exist? dir + mkdir_p dir + File.chmod 0777, dir + end + end + end + end + + def self.expected_paths + %w[ + . + .. + .dotfile + .dotsubdir + brace + deeply + dir + dir_filename_ordering + file_one.ext + file_two.ext + nested + nondotfile + special + subdir_one + subdir_two + ] + end + + def self.expected_glob_paths + expected_paths - ['..'] + end +end diff --git a/spec/ruby/core/dir/for_fd_spec.rb b/spec/ruby/core/dir/for_fd_spec.rb new file mode 100644 index 0000000000..ba467f2f86 --- /dev/null +++ b/spec/ruby/core/dir/for_fd_spec.rb @@ -0,0 +1,79 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' + +quarantine! do # leads to "Errno::EBADF: Bad file descriptor - closedir" in DirSpecs.delete_mock_dirs +ruby_version_is '3.3' do + platform_is_not :windows do + describe "Dir.for_fd" do + before :all do + DirSpecs.create_mock_dirs + end + + after :all do + DirSpecs.delete_mock_dirs + end + + before :each do + @original = Dir.pwd + end + + after :each do + Dir.chdir(@original) + end + + it "returns a new Dir object representing the directory specified by the given integer directory file descriptor" do + dir = Dir.new(DirSpecs.mock_dir) + dir_new = Dir.for_fd(dir.fileno) + + dir_new.should.instance_of?(Dir) + dir_new.children.should == dir.children + dir_new.fileno.should == dir.fileno + ensure + dir.close + end + + it "returns a new Dir object without associated path" do + dir = Dir.new(DirSpecs.mock_dir) + dir_new = Dir.for_fd(dir.fileno) + + dir_new.path.should == nil + ensure + dir.close + end + + it "calls #to_int to convert a value to an Integer" do + dir = Dir.new(DirSpecs.mock_dir) + obj = mock("fd") + obj.should_receive(:to_int).and_return(dir.fileno) + + dir_new = Dir.for_fd(obj) + dir_new.fileno.should == dir.fileno + ensure + dir.close + end + + it "raises TypeError when value cannot be converted to Integer" do + -> { + Dir.for_fd(nil) + }.should raise_error(TypeError, "no implicit conversion from nil to integer") + end + + it "raises a SystemCallError if the file descriptor given is not valid" do + -> { Dir.for_fd(-1) }.should raise_error(SystemCallError, "Bad file descriptor - fdopendir") + end + + it "raises a SystemCallError if the file descriptor given is not for a directory" do + -> { Dir.for_fd $stdout.fileno }.should raise_error(SystemCallError, "Not a directory - fdopendir") + end + end + end + + platform_is :windows do + describe "Dir.for_fd" do + it "raises NotImplementedError" do + -> { Dir.for_fd 1 }.should raise_error(NotImplementedError) + end + end + end +end +end diff --git a/spec/ruby/core/dir/foreach_spec.rb b/spec/ruby/core/dir/foreach_spec.rb new file mode 100644 index 0000000000..9cf34b1d71 --- /dev/null +++ b/spec/ruby/core/dir/foreach_spec.rb @@ -0,0 +1,68 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' + +describe "Dir.foreach" do + before :all do + DirSpecs.create_mock_dirs + end + + after :all do + DirSpecs.delete_mock_dirs + end + + it "yields all names in an existing directory to the provided block" do + a, b = [], [] + + Dir.foreach(DirSpecs.mock_dir) {|f| a << f} + Dir.foreach("#{DirSpecs.mock_dir}/deeply/nested") {|f| b << f} + + a.sort.should == DirSpecs.expected_paths + b.sort.should == %w|. .. .dotfile.ext directory| + end + + it "returns nil when successful" do + Dir.foreach(DirSpecs.mock_dir) {|f| f}.should == nil + end + + it "calls #to_path on non-String arguments" do + p = mock('path') + p.should_receive(:to_path).and_return(DirSpecs.mock_dir) + Dir.foreach(p).to_a + end + + it "raises a SystemCallError if passed a nonexistent directory" do + -> { Dir.foreach(DirSpecs.nonexistent) {} }.should raise_error(SystemCallError) + end + + it "returns an Enumerator if no block given" do + Dir.foreach(DirSpecs.mock_dir).should be_an_instance_of(Enumerator) + Dir.foreach(DirSpecs.mock_dir).to_a.sort.should == DirSpecs.expected_paths + end + + it "accepts an encoding keyword for the encoding of the entries" do + dirs = Dir.foreach("#{DirSpecs.mock_dir}/deeply/nested", encoding: "utf-8").to_a.sort + dirs.each { |dir| dir.encoding.should == Encoding::UTF_8 } + + dirs = Dir.foreach("#{DirSpecs.mock_dir}/deeply/nested", encoding: Encoding::ISO_8859_1).to_a.sort + dirs.each { |dir| dir.encoding.should == Encoding::ISO_8859_1 } + + Dir.foreach("#{DirSpecs.mock_dir}/deeply/nested", encoding: Encoding::ISO_8859_1) do |f| + f.encoding.should == Encoding::ISO_8859_1 + end + end + + describe "when no block is given" do + it "returns an Enumerator" do + Dir.foreach(DirSpecs.mock_dir).should be_an_instance_of(Enumerator) + Dir.foreach(DirSpecs.mock_dir).to_a.sort.should == DirSpecs.expected_paths + end + + describe "returned Enumerator" do + describe "size" do + it "should return nil" do + Dir.foreach(DirSpecs.mock_dir).size.should == nil + end + end + end + end +end diff --git a/spec/ruby/core/dir/getwd_spec.rb b/spec/ruby/core/dir/getwd_spec.rb new file mode 100644 index 0000000000..132634347c --- /dev/null +++ b/spec/ruby/core/dir/getwd_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' +require_relative 'shared/pwd' + +describe "Dir.getwd" do + before :all do + DirSpecs.create_mock_dirs + end + + after :all do + DirSpecs.delete_mock_dirs + end + + it_behaves_like :dir_pwd, :getwd +end diff --git a/spec/ruby/core/dir/glob_spec.rb b/spec/ruby/core/dir/glob_spec.rb new file mode 100644 index 0000000000..a60b233bc0 --- /dev/null +++ b/spec/ruby/core/dir/glob_spec.rb @@ -0,0 +1,362 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' +require_relative 'shared/glob' + +describe "Dir.glob" do + it_behaves_like :dir_glob, :glob +end + +describe "Dir.glob" do + it_behaves_like :dir_glob_recursive, :glob +end + +describe "Dir.glob" do + before :each do + DirSpecs.create_mock_dirs + + @cwd = Dir.pwd + Dir.chdir DirSpecs.mock_dir + end + + after :each do + Dir.chdir @cwd + + DirSpecs.delete_mock_dirs + end + + it "can take an array of patterns" do + Dir.glob(["file_o*", "file_t*"]).should == + %w!file_one.ext file_two.ext! + end + + it 'returns matching file paths when supplied :base keyword argument' do + dir = tmp('dir_glob_base') + file_1 = "#{dir}/lib/bloop.rb" + file_2 = "#{dir}/lib/soup.rb" + file_3 = "#{dir}/lib/mismatched_file_type.txt" + file_4 = "#{dir}/mismatched_directory.rb" + + touch file_1 + touch file_2 + touch file_3 + touch file_4 + + Dir.glob('**/*.rb', base: "#{dir}/lib").sort.should == ["bloop.rb", "soup.rb"].sort + ensure + rm_r dir + end + + it "calls #to_path to convert multiple patterns" do + pat1 = mock('file_one.ext') + pat1.should_receive(:to_path).and_return('file_one.ext') + pat2 = mock('file_two.ext') + pat2.should_receive(:to_path).and_return('file_two.ext') + + Dir.glob([pat1, pat2]).should == %w[file_one.ext file_two.ext] + end + + it "matches both dot and non-dotfiles with '*' and option File::FNM_DOTMATCH" do + Dir.glob('*', File::FNM_DOTMATCH).sort.should == DirSpecs.expected_glob_paths + end + + it "matches files with any beginning with '*<non-special characters>' and option File::FNM_DOTMATCH" do + Dir.glob('*file', File::FNM_DOTMATCH).sort.should == %w|.dotfile nondotfile|.sort + end + + it "matches any files in the current directory with '**' and option File::FNM_DOTMATCH" do + Dir.glob('**', File::FNM_DOTMATCH).sort.should == DirSpecs.expected_glob_paths + end + + it "recursively matches any subdirectories except './' or '../' with '**/' from the current directory and option File::FNM_DOTMATCH" do + expected = %w[ + .dotsubdir/ + brace/ + deeply/ + deeply/nested/ + deeply/nested/directory/ + deeply/nested/directory/structure/ + dir/ + nested/ + nested/.dotsubir/ + special/ + special/test\ +()[]{}/ + special/test{1}/ + special/{}/ + subdir_one/ + subdir_two/ + ] + + Dir.glob('**/', File::FNM_DOTMATCH).sort.should == expected + end + + it "recursively matches files and directories in nested dot subdirectory except . with 'nested/**/*' from the current directory and option File::FNM_DOTMATCH" do + expected = %w[ + nested/. + nested/.dotsubir + nested/.dotsubir/.dotfile + nested/.dotsubir/nondotfile + ] + + Dir.glob('nested/**/*', File::FNM_DOTMATCH).sort.should == expected.sort + end + + # This is a separate case to check **/ coming after a constant + # directory as well. + it "recursively matches any subdirectories except './' or '../' with '**/' and option File::FNM_DOTMATCH" do + expected = %w[ + ./ + ./.dotsubdir/ + ./brace/ + ./deeply/ + ./deeply/nested/ + ./deeply/nested/directory/ + ./deeply/nested/directory/structure/ + ./dir/ + ./nested/ + ./nested/.dotsubir/ + ./special/ + ./special/test\ +()[]{}/ + ./special/test{1}/ + ./special/{}/ + ./subdir_one/ + ./subdir_two/ + ] + + Dir.glob('./**/', File::FNM_DOTMATCH).sort.should == expected + end + + it "matches a list of paths by concatenating their individual results" do + expected = %w[ + deeply/ + deeply/nested/ + deeply/nested/directory/ + deeply/nested/directory/structure/ + subdir_two/nondotfile + subdir_two/nondotfile.ext + ] + + Dir.glob('{deeply/**/,subdir_two/*}').sort.should == expected + end + + it "preserves multiple /s before a **" do + expected = %w[ + deeply//nested/directory/structure + ] + + Dir.glob('{deeply//**/structure}').sort.should == expected + end + + it "accepts a block and yields it with each elements" do + ary = [] + ret = Dir.glob(["file_o*", "file_t*"]) { |t| ary << t } + ret.should be_nil + ary.should == %w!file_one.ext file_two.ext! + end + + it "ignores non-dirs when traversing recursively" do + touch "spec" + Dir.glob("spec/**/*.rb").should == [] + end + + it "matches nothing when given an empty list of paths" do + Dir.glob('{}').should == [] + end + + it "handles infinite directory wildcards" do + Dir.glob('**/**/**').should_not.empty? + end + + it "handles **/** with base keyword argument" do + Dir.glob('**/**', base: "dir").should == ["filename_ordering"] + + expected = %w[ + nested + nested/directory + nested/directory/structure + nested/directory/structure/bar + nested/directory/structure/baz + nested/directory/structure/file_one + nested/directory/structure/file_one.ext + nested/directory/structure/foo + nondotfile + ].sort + + Dir.glob('**/**', base: "deeply").sort.should == expected + end + + it "handles **/ with base keyword argument" do + expected = %w[ + / + directory/ + directory/structure/ + ] + Dir.glob('**/', base: "deeply/nested").sort.should == expected + end + + it "handles **/nondotfile with base keyword argument" do + expected = %w[ + deeply/nondotfile + nondotfile + subdir_one/nondotfile + subdir_two/nondotfile + ] + Dir.glob('**/nondotfile', base: ".").sort.should == expected + end + + it "handles **/nondotfile with base keyword argument and FNM_DOTMATCH" do + expected = %w[ + .dotsubdir/nondotfile + deeply/nondotfile + nested/.dotsubir/nondotfile + nondotfile + subdir_one/nondotfile + subdir_two/nondotfile + ] + Dir.glob('**/nondotfile', File::FNM_DOTMATCH, base: ".").sort.should == expected + end + + it "handles **/.dotfile with base keyword argument" do + expected = %w[ + .dotfile + deeply/.dotfile + subdir_one/.dotfile + ] + Dir.glob('**/.dotfile', base: ".").sort.should == expected + end + + it "handles **/.dotfile with base keyword argument and FNM_DOTMATCH" do + expected = %w[ + .dotfile + .dotsubdir/.dotfile + deeply/.dotfile + nested/.dotsubir/.dotfile + subdir_one/.dotfile + ] + Dir.glob('**/.dotfile', File::FNM_DOTMATCH, base: ".").sort.should == expected + end + + it "handles **/.* with base keyword argument" do + expected = %w[ + .dotfile.ext + directory/structure/.ext + ].sort + + Dir.glob('**/.*', base: "deeply/nested").sort.should == expected + end + + it "handles **/.* with base keyword argument and FNM_DOTMATCH" do + expected = %w[ + . + .dotfile.ext + directory/structure/.ext + ].sort + + Dir.glob('**/.*', File::FNM_DOTMATCH, base: "deeply/nested").sort.should == expected + end + + it "handles **/** with base keyword argument and FNM_DOTMATCH" do + expected = %w[ + . + .dotfile.ext + directory + directory/structure + directory/structure/.ext + directory/structure/bar + directory/structure/baz + directory/structure/file_one + directory/structure/file_one.ext + directory/structure/foo + ].sort + + Dir.glob('**/**', File::FNM_DOTMATCH, base: "deeply/nested").sort.should == expected + end + + it "handles **/*pattern* with base keyword argument and FNM_DOTMATCH" do + expected = %w[ + .dotfile.ext + directory/structure/file_one + directory/structure/file_one.ext + ] + + Dir.glob('**/*file*', File::FNM_DOTMATCH, base: "deeply/nested").sort.should == expected + end + + it "handles **/glob with base keyword argument and FNM_EXTGLOB" do + expected = %w[ + directory/structure/bar + directory/structure/file_one + directory/structure/file_one.ext + ] + + Dir.glob('**/*{file,bar}*', File::FNM_EXTGLOB, base: "deeply/nested").sort.should == expected + end + + it "handles simple filename patterns" do + Dir.glob('.dotfile').should == ['.dotfile'] + end + + it "handles simple directory patterns" do + Dir.glob('.dotsubdir/').should == ['.dotsubdir/'] + end + + it "handles simple directory patterns applied to non-directories" do + Dir.glob('nondotfile/').should == [] + end + + platform_is_not(:windows) do + it "matches the literal character '\\' with option File::FNM_NOESCAPE" do + Dir.mkdir 'foo?bar' + + begin + Dir.glob('foo?bar', File::FNM_NOESCAPE).should == %w|foo?bar| + Dir.glob('foo\?bar', File::FNM_NOESCAPE).should == [] + ensure + Dir.rmdir 'foo?bar' + end + + Dir.mkdir 'foo\?bar' + + begin + Dir.glob('foo\?bar', File::FNM_NOESCAPE).should == %w|foo\\?bar| + ensure + Dir.rmdir 'foo\?bar' + end + end + + it "returns nil for directories current user has no permission to read" do + Dir.mkdir('no_permission') + File.chmod(0, 'no_permission') + + begin + Dir.glob('no_permission/*').should == [] + ensure + Dir.rmdir('no_permission') + end + end + + it "will follow symlinks when processing a `*/` pattern." do + expected = ['special/ln/nondotfile'] + Dir.glob('special/*/nondotfile').should == expected + end + + it "will not follow symlinks when recursively traversing directories" do + expected = %w[ + deeply/nondotfile + nondotfile + subdir_one/nondotfile + subdir_two/nondotfile + ] + Dir.glob('**/nondotfile').sort.should == expected + end + + it "will follow symlinks when testing directory after recursive directory in pattern" do + expected = %w[ + deeply/nondotfile + special/ln/nondotfile + subdir_one/nondotfile + subdir_two/nondotfile + ] + Dir.glob('**/*/nondotfile').sort.should == expected + end + end +end diff --git a/spec/ruby/core/dir/home_spec.rb b/spec/ruby/core/dir/home_spec.rb new file mode 100644 index 0000000000..966ac38af3 --- /dev/null +++ b/spec/ruby/core/dir/home_spec.rb @@ -0,0 +1,85 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' + +describe "Dir.home" do + before :each do + @home = ENV['HOME'] + ENV['HOME'] = "/rubyspec_home" + end + + after :each do + ENV['HOME'] = @home + end + + describe "when called without arguments" do + it "returns the current user's home directory, reading $HOME first" do + Dir.home.should == "/rubyspec_home" + end + + it "returns a non-frozen string" do + Dir.home.should_not.frozen? + end + + it "returns a string with the filesystem encoding" do + Dir.home.encoding.should == Encoding.find("filesystem") + end + + platform_is_not :windows do + it "works even if HOME is unset" do + ENV.delete('HOME') + Dir.home.should.start_with?('/') + Dir.home.encoding.should == Encoding.find("filesystem") + end + end + + platform_is :windows do + it "returns the home directory with forward slashs and as UTF-8" do + ENV['HOME'] = "C:\\rubyspäc\\home" + home = Dir.home + home.should == "C:/rubyspäc/home" + home.encoding.should == Encoding::UTF_8 + end + + it "retrieves the directory from HOME, USERPROFILE, HOMEDRIVE/HOMEPATH and the WinAPI in that order" do + old_dirs = [ENV.delete('HOME'), ENV.delete('USERPROFILE'), ENV.delete('HOMEDRIVE'), ENV.delete('HOMEPATH')] + + Dir.home.should == old_dirs[1].gsub("\\", "/") + ENV['HOMEDRIVE'] = "C:" + ENV['HOMEPATH'] = "\\rubyspec\\home1" + Dir.home.should == "C:/rubyspec/home1" + ENV['USERPROFILE'] = "C:\\rubyspec\\home2" + Dir.home.should == "C:/rubyspec/home2" + ENV['HOME'] = "C:\\rubyspec\\home3" + Dir.home.should == "C:/rubyspec/home3" + ensure + ENV['HOME'], ENV['USERPROFILE'], ENV['HOMEDRIVE'], ENV['HOMEPATH'] = *old_dirs + end + end + end + + describe "when called with the current user name" do + platform_is_not :windows, :android, :wasi do + it "returns the named user's home directory, from the user database" do + Dir.home(ENV['USER']).should == `echo ~#{ENV['USER']}`.chomp + end + end + + it "returns a non-frozen string" do + Dir.home(ENV['USER']).should_not.frozen? + end + + it "returns a string with the filesystem encoding" do + Dir.home(ENV['USER']).encoding.should == Encoding.find("filesystem") + end + end + + it "raises an ArgumentError if the named user doesn't exist" do + -> { Dir.home('geuw2n288dh2k') }.should raise_error(ArgumentError) + end + + describe "when called with a nil user name" do + it "returns the current user's home directory, reading $HOME first" do + Dir.home(nil).should == "/rubyspec_home" + end + end +end diff --git a/spec/ruby/core/dir/initialize_spec.rb b/spec/ruby/core/dir/initialize_spec.rb new file mode 100644 index 0000000000..547b7dc18e --- /dev/null +++ b/spec/ruby/core/dir/initialize_spec.rb @@ -0,0 +1,23 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' + +describe "Dir#initialize" do + before :each do + DirSpecs.create_mock_dirs + end + + after :each do + DirSpecs.delete_mock_dirs + end + + it "calls #to_path on non-String arguments" do + p = mock('path') + p.stub!(:to_path).and_return(DirSpecs.mock_dir) + dir = Dir.new(p) + begin + dir.path.should == DirSpecs.mock_dir + ensure + dir.close + end + end +end diff --git a/spec/ruby/core/dir/inspect_spec.rb b/spec/ruby/core/dir/inspect_spec.rb new file mode 100644 index 0000000000..37338a97d4 --- /dev/null +++ b/spec/ruby/core/dir/inspect_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' + +describe "Dir#inspect" do + before :each do + @dir = Dir.new(Dir.getwd) + end + + after :each do + @dir.close + end + + it "returns a String" do + @dir.inspect.should be_an_instance_of(String) + end + + it "includes the class name" do + @dir.inspect.should =~ /Dir/ + end + + it "includes the directory name" do + @dir.inspect.should include(Dir.getwd) + end +end diff --git a/spec/ruby/core/dir/mkdir_spec.rb b/spec/ruby/core/dir/mkdir_spec.rb new file mode 100644 index 0000000000..076ec19dd9 --- /dev/null +++ b/spec/ruby/core/dir/mkdir_spec.rb @@ -0,0 +1,107 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' + +describe "Dir.mkdir" do + before :all do + DirSpecs.create_mock_dirs + end + + after :all do + DirSpecs.delete_mock_dirs + end + + it "creates the named directory with the given permissions" do + DirSpecs.clear_dirs + + nonexisting = DirSpecs.mock_dir('nonexisting') + default_perms = DirSpecs.mock_dir('default_perms') + reduced = DirSpecs.mock_dir('reduced') + begin + File.should_not.exist?(nonexisting) + Dir.mkdir nonexisting + File.should.exist?(nonexisting) + platform_is_not :windows do + Dir.mkdir default_perms + a = File.stat(default_perms).mode + Dir.mkdir reduced, (a - 1) + File.stat(reduced).mode.should_not == a + end + platform_is :windows do + Dir.mkdir default_perms, 0666 + a = File.stat(default_perms).mode + Dir.mkdir reduced, 0444 + File.stat(reduced).mode.should_not == a + end + + always_returns_0 = DirSpecs.mock_dir('always_returns_0') + Dir.mkdir(always_returns_0).should == 0 + platform_is_not(:windows) do + File.chmod(0777, nonexisting, default_perms, reduced, always_returns_0) + end + platform_is_not(:windows) do + File.chmod(0644, nonexisting, default_perms, reduced, always_returns_0) + end + ensure + DirSpecs.clear_dirs + end + end + + it "calls #to_path on non-String path arguments" do + DirSpecs.clear_dirs + p = mock('path') + p.should_receive(:to_path).and_return(DirSpecs.mock_dir('nonexisting')) + Dir.mkdir(p) + DirSpecs.clear_dirs + end + + it "calls #to_int on non-Integer permissions argument" do + DirSpecs.clear_dirs + path = DirSpecs.mock_dir('nonexisting') + permissions = mock('permissions') + permissions.should_receive(:to_int).and_return(0666) + Dir.mkdir(path, permissions) + DirSpecs.clear_dirs + end + + it "raises TypeError if non-Integer permissions argument does not have #to_int method" do + path = DirSpecs.mock_dir('nonexisting') + permissions = Object.new + + -> { Dir.mkdir(path, permissions) }.should raise_error(TypeError, 'no implicit conversion of Object into Integer') + end + + it "raises a SystemCallError if any of the directories in the path before the last does not exist" do + -> { Dir.mkdir "#{DirSpecs.nonexistent}/subdir" }.should raise_error(SystemCallError) + end + + it "raises Errno::EEXIST if the specified directory already exists" do + -> { Dir.mkdir("#{DirSpecs.mock_dir}/dir") }.should raise_error(Errno::EEXIST) + end + + it "raises Errno::EEXIST if the argument points to the existing file" do + -> { Dir.mkdir("#{DirSpecs.mock_dir}/file_one.ext") }.should raise_error(Errno::EEXIST) + end +end + +# The permissions flag are not supported on Windows as stated in documentation: +# The permissions may be modified by the value of File.umask, and are ignored on NT. +platform_is_not :windows do + as_user do + describe "Dir.mkdir" do + before :each do + @dir = tmp "noperms" + end + + after :each do + File.chmod 0777, @dir + rm_r @dir + end + + it "raises a SystemCallError when lacking adequate permissions in the parent dir" do + Dir.mkdir @dir, 0000 + + -> { Dir.mkdir "#{@dir}/subdir" }.should raise_error(SystemCallError) + end + end + end +end diff --git a/spec/ruby/core/dir/open_spec.rb b/spec/ruby/core/dir/open_spec.rb new file mode 100644 index 0000000000..27f362320b --- /dev/null +++ b/spec/ruby/core/dir/open_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' +require_relative 'shared/open' + +describe "Dir.open" do + before :all do + DirSpecs.create_mock_dirs + end + + after :all do + DirSpecs.delete_mock_dirs + end + + it_behaves_like :dir_open, :open +end diff --git a/spec/ruby/core/dir/path_spec.rb b/spec/ruby/core/dir/path_spec.rb new file mode 100644 index 0000000000..b1c24c406b --- /dev/null +++ b/spec/ruby/core/dir/path_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' +require_relative 'shared/path' + +describe "Dir#path" do + before :all do + DirSpecs.create_mock_dirs + end + + after :all do + DirSpecs.delete_mock_dirs + end + + it_behaves_like :dir_path, :path +end diff --git a/spec/ruby/core/dir/pos_spec.rb b/spec/ruby/core/dir/pos_spec.rb new file mode 100644 index 0000000000..b382bff81f --- /dev/null +++ b/spec/ruby/core/dir/pos_spec.rb @@ -0,0 +1,40 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' +require_relative 'shared/closed' +require_relative 'shared/pos' + +describe "Dir#pos" do + before :all do + DirSpecs.create_mock_dirs + end + + after :all do + DirSpecs.delete_mock_dirs + end + + it_behaves_like :dir_pos, :pos +end + +describe "Dir#pos" do + before :all do + DirSpecs.create_mock_dirs + end + + after :all do + DirSpecs.delete_mock_dirs + end + + it_behaves_like :dir_closed, :pos +end + +describe "Dir#pos=" do + before :all do + DirSpecs.create_mock_dirs + end + + after :all do + DirSpecs.delete_mock_dirs + end + + it_behaves_like :dir_pos_set, :pos= +end diff --git a/spec/ruby/core/dir/pwd_spec.rb b/spec/ruby/core/dir/pwd_spec.rb new file mode 100644 index 0000000000..ad01286c90 --- /dev/null +++ b/spec/ruby/core/dir/pwd_spec.rb @@ -0,0 +1,39 @@ +# -*- encoding: utf-8 -*- +require_relative '../../spec_helper' +require_relative 'fixtures/common' +require_relative 'shared/pwd' + +describe "Dir.pwd" do + before :all do + DirSpecs.create_mock_dirs + end + + after :all do + DirSpecs.delete_mock_dirs + end + + it_behaves_like :dir_pwd, :pwd +end + +describe "Dir.pwd" do + before :each do + @name = tmp("あ").force_encoding('binary') + @fs_encoding = Encoding.find('filesystem') + end + + after :each do + rm_r @name + end + + platform_is_not :windows do + it "correctly handles dirs with unicode characters in them" do + Dir.mkdir @name + Dir.chdir @name do + if @fs_encoding == Encoding::UTF_8 + Dir.pwd.encoding.should == Encoding::UTF_8 + end + Dir.pwd.force_encoding('binary').should == @name + end + end + end +end diff --git a/spec/ruby/core/dir/read_spec.rb b/spec/ruby/core/dir/read_spec.rb new file mode 100644 index 0000000000..276930c6b7 --- /dev/null +++ b/spec/ruby/core/dir/read_spec.rb @@ -0,0 +1,76 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' +require_relative 'shared/closed' + +describe "Dir#read" do + before :all do + DirSpecs.create_mock_dirs + end + + after :all do + DirSpecs.delete_mock_dirs + end + + it "returns the file name in the current seek position" do + # an FS does not necessarily impose order + ls = Dir.entries DirSpecs.mock_dir + dir = Dir.open DirSpecs.mock_dir + ls.should include(dir.read) + dir.close + end + + it "returns nil when there are no more entries" do + dir = Dir.open DirSpecs.mock_dir + DirSpecs.expected_paths.size.times do + dir.read.should_not == nil + end + dir.read.should == nil + dir.close + end + + it "returns each entry successively" do + dir = Dir.open DirSpecs.mock_dir + entries = [] + while entry = dir.read + entries << entry + end + dir.close + + entries.sort.should == DirSpecs.expected_paths + end + + platform_is_not :windows do + it "returns all directory entries even when encoding conversion will fail" do + dir = Dir.open(File.join(DirSpecs.mock_dir, 'special')) + utf8_entries = [] + begin + while entry = dir.read + utf8_entries << entry + end + ensure + dir.close + end + old_internal_encoding = Encoding::default_internal + old_external_encoding = Encoding::default_external + Encoding.default_internal = Encoding::UTF_8 + Encoding.default_external = Encoding::SHIFT_JIS + shift_jis_entries = [] + begin + Dir.open(File.join(DirSpecs.mock_dir, 'special')) do |d| + -> { + while entry = d.read + shift_jis_entries << entry + end + }.should_not raise_error + end + ensure + Encoding.default_internal = old_internal_encoding + Encoding.default_external = old_external_encoding + end + shift_jis_entries.size.should == utf8_entries.size + shift_jis_entries.filter { |f| f.encoding == Encoding::SHIFT_JIS }.size.should == 1 + end + end + + it_behaves_like :dir_closed, :read +end diff --git a/spec/ruby/core/dir/rewind_spec.rb b/spec/ruby/core/dir/rewind_spec.rb new file mode 100644 index 0000000000..220d7f5372 --- /dev/null +++ b/spec/ruby/core/dir/rewind_spec.rb @@ -0,0 +1,36 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' +require_relative 'shared/closed' + +describe "Dir#rewind" do + before :all do + DirSpecs.create_mock_dirs + end + + after :all do + DirSpecs.delete_mock_dirs + end + + before :each do + @dir = Dir.open DirSpecs.mock_dir + end + + after :each do + @dir.close + end + + it "resets the next read to start from the first entry" do + a = @dir.read + b = @dir.read + a.should_not == b + @dir.rewind + c = @dir.read + c.should == a + end + + it "returns the Dir instance" do + @dir.rewind.should == @dir + end + + it_behaves_like :dir_closed, :rewind +end diff --git a/spec/ruby/core/dir/rmdir_spec.rb b/spec/ruby/core/dir/rmdir_spec.rb new file mode 100644 index 0000000000..08cd1a5bc6 --- /dev/null +++ b/spec/ruby/core/dir/rmdir_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' +require_relative 'shared/delete' + +describe "Dir.rmdir" do + before :all do + DirSpecs.create_mock_dirs + end + + after :all do + DirSpecs.delete_mock_dirs + end + + it_behaves_like :dir_delete, :rmdir +end diff --git a/spec/ruby/core/dir/seek_spec.rb b/spec/ruby/core/dir/seek_spec.rb new file mode 100644 index 0000000000..ed409897cd --- /dev/null +++ b/spec/ruby/core/dir/seek_spec.rb @@ -0,0 +1,19 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' +require_relative 'shared/pos' + +describe "Dir#seek" do + before :all do + DirSpecs.create_mock_dirs + end + + after :all do + DirSpecs.delete_mock_dirs + end + + it "returns the Dir instance" do + @dir.seek(@dir.pos).should == @dir + end + + it_behaves_like :dir_pos_set, :seek +end diff --git a/spec/ruby/core/dir/shared/chroot.rb b/spec/ruby/core/dir/shared/chroot.rb new file mode 100644 index 0000000000..a8f7c10a19 --- /dev/null +++ b/spec/ruby/core/dir/shared/chroot.rb @@ -0,0 +1,44 @@ +describe :dir_chroot_as_root, shared: true do + before :all do + DirSpecs.create_mock_dirs + + @real_root = "../" * (__dir__.count('/') - 1) + @ref_dir = File.join("/", File.basename(Dir["/*"].first)) + end + + after :all do + until File.exist?(@ref_dir) + Dir.send(@method, "../") or break + end + + DirSpecs.delete_mock_dirs + end + + # Pending until https://github.com/ruby/ruby/runs/8075149420 is fixed + compilations_ci = ENV["GITHUB_WORKFLOW"] == "Compilations" + + it "can be used to change the process' root directory" do + -> { Dir.send(@method, __dir__) }.should_not raise_error + File.should.exist?("/#{File.basename(__FILE__)}") + end unless compilations_ci + + it "returns 0 if successful" do + Dir.send(@method, '/').should == 0 + end + + it "raises an Errno::ENOENT exception if the directory doesn't exist" do + -> { Dir.send(@method, 'xgwhwhsjai2222jg') }.should raise_error(Errno::ENOENT) + end + + it "can be escaped from with ../" do + Dir.send(@method, @real_root) + File.should.exist?(@ref_dir) + File.should_not.exist?("/#{File.basename(__FILE__)}") + end unless compilations_ci + + it "calls #to_path on non-String argument" do + p = mock('path') + p.should_receive(:to_path).and_return(@real_root) + Dir.send(@method, p) + end +end diff --git a/spec/ruby/core/dir/shared/closed.rb b/spec/ruby/core/dir/shared/closed.rb new file mode 100644 index 0000000000..17d8332c2a --- /dev/null +++ b/spec/ruby/core/dir/shared/closed.rb @@ -0,0 +1,9 @@ +describe :dir_closed, shared: true do + it "raises an IOError when called on a closed Dir instance" do + -> { + dir = Dir.open DirSpecs.mock_dir + dir.close + dir.send(@method) {} + }.should raise_error(IOError) + end +end diff --git a/spec/ruby/core/dir/shared/delete.rb b/spec/ruby/core/dir/shared/delete.rb new file mode 100644 index 0000000000..a81b059759 --- /dev/null +++ b/spec/ruby/core/dir/shared/delete.rb @@ -0,0 +1,53 @@ +describe :dir_delete, shared: true do + before :each do + DirSpecs.rmdir_dirs true + end + + after :each do + DirSpecs.rmdir_dirs false + end + + it "removes empty directories" do + Dir.send(@method, DirSpecs.mock_rmdir("empty")).should == 0 + end + + it "calls #to_path on non-String arguments" do + p = mock('path') + p.should_receive(:to_path).and_return(DirSpecs.mock_rmdir("empty")) + Dir.send(@method, p) + end + + it "raises an Errno::ENOTEMPTY when trying to remove a nonempty directory" do + -> do + Dir.send @method, DirSpecs.mock_rmdir("nonempty") + end.should raise_error(Errno::ENOTEMPTY) + end + + it "raises an Errno::ENOENT when trying to remove a non-existing directory" do + -> do + Dir.send @method, DirSpecs.nonexistent + end.should raise_error(Errno::ENOENT) + end + + it "raises an Errno::ENOTDIR when trying to remove a non-directory" do + file = DirSpecs.mock_rmdir("nonempty/regular") + touch(file) + -> do + Dir.send @method, file + end.should raise_error(Errno::ENOTDIR) + end + + # this won't work on Windows, since chmod(0000) does not remove all permissions + platform_is_not :windows do + as_user do + it "raises an Errno::EACCES if lacking adequate permissions to remove the directory" do + parent = DirSpecs.mock_rmdir("noperm") + child = DirSpecs.mock_rmdir("noperm", "child") + File.chmod(0000, parent) + -> do + Dir.send @method, child + end.should raise_error(Errno::EACCES) + end + end + end +end diff --git a/spec/ruby/core/dir/shared/exist.rb b/spec/ruby/core/dir/shared/exist.rb new file mode 100644 index 0000000000..3097f57715 --- /dev/null +++ b/spec/ruby/core/dir/shared/exist.rb @@ -0,0 +1,57 @@ +describe :dir_exist, shared: true do + it "returns true if the given directory exists" do + Dir.send(@method, __dir__).should be_true + end + + it "returns true for '.'" do + Dir.send(@method, '.').should be_true + end + + it "returns true for '..'" do + Dir.send(@method, '..').should be_true + end + + it "understands non-ASCII paths" do + subdir = File.join(tmp("\u{9876}\u{665}")) + Dir.send(@method, subdir).should be_false + Dir.mkdir(subdir) + Dir.send(@method, subdir).should be_true + Dir.rmdir(subdir) + end + + it "understands relative paths" do + Dir.send(@method, __dir__ + '/../').should be_true + end + + it "returns false if the given directory doesn't exist" do + Dir.send(@method, 'y26dg27n2nwjs8a/').should be_false + end + + it "doesn't require the name to have a trailing slash" do + dir = __dir__ + dir.sub!(/\/$/,'') + Dir.send(@method, dir).should be_true + end + + it "doesn't expand paths" do + skip "$HOME not valid directory" unless ENV['HOME'] && File.directory?(ENV['HOME']) + Dir.send(@method, File.expand_path('~')).should be_true + Dir.send(@method, '~').should be_false + end + + it "returns false if the argument exists but is a file" do + File.should.exist?(__FILE__) + Dir.send(@method, __FILE__).should be_false + end + + it "doesn't set $! when file doesn't exist" do + Dir.send(@method, "/path/to/non/existent/dir") + $!.should be_nil + end + + it "calls #to_path on non String arguments" do + p = mock('path') + p.should_receive(:to_path).and_return(__dir__) + Dir.send(@method, p) + end +end diff --git a/spec/ruby/core/dir/shared/glob.rb b/spec/ruby/core/dir/shared/glob.rb new file mode 100644 index 0000000000..b1fc924f08 --- /dev/null +++ b/spec/ruby/core/dir/shared/glob.rb @@ -0,0 +1,441 @@ +# -*- encoding: utf-8 -*- +describe :dir_glob, shared: true do + before :all do + DirSpecs.create_mock_dirs + @cwd = Dir.pwd + Dir.chdir DirSpecs.mock_dir + end + + after :all do + Dir.chdir @cwd + DirSpecs.delete_mock_dirs + end + + it "raises an Encoding::CompatibilityError if the argument encoding is not compatible with US-ASCII" do + pattern = "files*".dup.force_encoding Encoding::UTF_16BE + -> { Dir.send(@method, pattern) }.should raise_error(Encoding::CompatibilityError) + end + + it "calls #to_path to convert a pattern" do + obj = mock('file_one.ext') + obj.should_receive(:to_path).and_return('file_one.ext') + + Dir.send(@method, obj).should == %w[file_one.ext] + end + + it "raises an ArgumentError if the string contains \\0" do + -> {Dir.send(@method, "file_o*\0file_t*")}.should raise_error ArgumentError, /nul-separated/ + end + + it "result is sorted by default" do + result = Dir.send(@method, '*') + result.should == result.sort + end + + it "result is sorted with sort: true" do + result = Dir.send(@method, '*', sort: true) + result.should == result.sort + end + + it "sort: false returns same files" do + result = Dir.send(@method,'*', sort: false) + result.sort.should == Dir.send(@method, '*').sort + end + + it "raises an ArgumentError if sort: is not true or false" do + -> { Dir.send(@method, '*', sort: 0) }.should raise_error ArgumentError, /expected true or false/ + -> { Dir.send(@method, '*', sort: nil) }.should raise_error ArgumentError, /expected true or false/ + -> { Dir.send(@method, '*', sort: 'false') }.should raise_error ArgumentError, /expected true or false/ + end + + it "matches non-dotfiles with '*'" do + expected = %w[ + brace + deeply + dir + dir_filename_ordering + file_one.ext + file_two.ext + nested + nondotfile + special + subdir_one + subdir_two + ] + + Dir.send(@method,'*').sort.should == expected + end + + it "returns empty array when empty pattern provided" do + Dir.send(@method, '').should == [] + end + + it "matches regexp special +" do + Dir.send(@method, 'special/+').should == ['special/+'] + end + + it "matches directories with special characters when escaped" do + Dir.send(@method, 'special/\{}/special').should == ["special/{}/special"] + end + + platform_is_not :windows do + it "matches regexp special *" do + Dir.send(@method, 'special/\*').should == ['special/*'] + end + + it "matches regexp special ?" do + Dir.send(@method, 'special/\?').should == ['special/?'] + end + + it "matches regexp special |" do + Dir.send(@method, 'special/|').should == ['special/|'] + end + + it "matches files with backslashes in their name" do + Dir.glob('special/\\\\{a,b}').should == ['special/\a'] + end + + it "matches directory with special characters in their name in complex patterns" do + Dir.glob("special/test +()\\[\\]\\{\\}/hello_world{.{en},}{.{html},}{+{phone},}{.{erb},}").should == ['special/test +()[]{}/hello_world.erb'] + end + end + + it "matches regexp special ^" do + Dir.send(@method, 'special/^').should == ['special/^'] + end + + it "matches regexp special $" do + Dir.send(@method, 'special/$').should == ['special/$'] + end + + it "matches regexp special (" do + Dir.send(@method, 'special/(').should == ['special/('] + end + + it "matches regexp special )" do + Dir.send(@method, 'special/)').should == ['special/)'] + end + + it "matches regexp special [" do + Dir.send(@method, 'special/\[').should == ['special/['] + end + + it "matches regexp special ]" do + Dir.send(@method, 'special/]').should == ['special/]'] + end + + it "matches regexp special {" do + Dir.send(@method, 'special/\{').should == ['special/{'] + end + + it "matches regexp special }" do + Dir.send(@method, 'special/\}').should == ['special/}'] + end + + it "matches paths with glob patterns" do + Dir.send(@method, 'special/test\{1\}/*').should == ['special/test{1}/file[1]'] + end + + it "matches dotfiles except .. with '.*'" do + Dir.send(@method, '.*').sort.should == %w|. .dotfile .dotsubdir|.sort + end + + it "matches non-dotfiles with '*<non-special characters>'" do + Dir.send(@method, '*file').sort.should == %w|nondotfile|.sort + end + + it "matches dotfiles with '.*<non-special characters>'" do + Dir.send(@method, '.*file').sort.should == %w|.dotfile|.sort + end + + it "matches files with any ending with '<non-special characters>*'" do + Dir.send(@method, 'file*').sort.should == %w|file_one.ext file_two.ext|.sort + end + + it "matches files with any middle with '<non-special characters>*<non-special characters>'" do + Dir.send(@method, 'sub*_one').sort.should == %w|subdir_one|.sort + end + + it "handles directories with globs" do + Dir.send(@method, 'sub*/*').sort.should == %w!subdir_one/nondotfile subdir_two/nondotfile subdir_two/nondotfile.ext! + end + + it "matches files with multiple '*' special characters" do + Dir.send(@method, '*fi*e*').sort.should == %w|dir_filename_ordering nondotfile file_one.ext file_two.ext|.sort + end + + it "matches non-dotfiles in the current directory with '**'" do + expected = %w[ + brace + deeply + dir + dir_filename_ordering + file_one.ext + file_two.ext + nested + nondotfile + special + subdir_one + subdir_two + ] + + Dir.send(@method, '**').sort.should == expected + end + + it "matches dotfiles in the current directory except .. with '.**'" do + Dir.send(@method, '.**').sort.should == %w|. .dotsubdir .dotfile|.sort + end + + it "recursively matches any nondot subdirectories with '**/'" do + expected = %w[ + brace/ + deeply/ + deeply/nested/ + deeply/nested/directory/ + deeply/nested/directory/structure/ + dir/ + nested/ + special/ + special/test\ +()[]{}/ + special/test{1}/ + special/{}/ + subdir_one/ + subdir_two/ + ] + + Dir.send(@method, '**/').sort.should == expected + end + + it "recursively matches any subdirectories except './' or '../' with '**/' from the base directory if that is specified" do + expected = %w[ + nested/directory + ] + + Dir.send(@method, '**/*ory', base: 'deeply').sort.should == expected + end + + it "recursively matches any subdirectories including ./ with '.**/'" do + Dir.chdir("#{DirSpecs.mock_dir}/subdir_one") do + Dir.send(@method, '.**/').should == ['./'] + end + end + + it "matches a single character except leading '.' with '?'" do + Dir.send(@method, '?ubdir_one').sort.should == %w|subdir_one|.sort + end + + it "accepts multiple '?' characters in a pattern" do + Dir.send(@method, 'subdir_???').sort.should == %w|subdir_one subdir_two|.sort + end + + it "matches any characters in a set with '[<characters>]'" do + Dir.send(@method, '[stfu]ubdir_one').sort.should == %w|subdir_one|.sort + end + + it "matches any characters in a range with '[<character>-<character>]'" do + Dir.send(@method, '[a-zA-Z]ubdir_one').sort.should == %w|subdir_one|.sort + end + + it "matches any characters except those in a set with '[^<characters>]'" do + Dir.send(@method, '[^wtf]ubdir_one').sort.should == %w|subdir_one|.sort + end + + it "matches any characters except those in a range with '[^<character>-<character]'" do + Dir.send(@method, '[^0-9]ubdir_one').sort.should == %w|subdir_one|.sort + end + + it "matches any one of the strings in a set with '{<string>,<other>,...}'" do + Dir.send(@method, 'subdir_{one,two,three}').sort.should == %w|subdir_one subdir_two|.sort + end + + it "matches a set '{<string>,<other>,...}' which also uses a glob" do + Dir.send(@method, 'sub*_{one,two,three}').sort.should == %w|subdir_one subdir_two|.sort + end + + it "accepts string sets with empty strings with {<string>,,<other>}" do + a = Dir.send(@method, 'deeply/nested/directory/structure/file_one{.ext,}').sort + a.should == %w|deeply/nested/directory/structure/file_one.ext + deeply/nested/directory/structure/file_one|.sort + end + + it "matches dot or non-dotfiles with '{,.}*'" do + Dir.send(@method, '{,.}*').sort.should == DirSpecs.expected_glob_paths + end + + it "respects the order of {} expressions, expanding left most first" do + files = Dir.send(@method, "brace/a{.js,.html}{.erb,.rjs}") + files.should == %w!brace/a.js.rjs brace/a.html.erb! + end + + it "respects the optional nested {} expressions" do + files = Dir.send(@method, "brace/a{.{js,html},}{.{erb,rjs},}") + files.should == %w!brace/a.js.rjs brace/a.js brace/a.html.erb brace/a.erb brace/a! + end + + it "matches special characters by escaping with a backslash with '\\<character>'" do + Dir.mkdir 'foo^bar' + + begin + Dir.send(@method, 'foo?bar').should == %w|foo^bar| + Dir.send(@method, 'foo\?bar').should == [] + Dir.send(@method, 'nond\otfile').should == %w|nondotfile| + ensure + Dir.rmdir 'foo^bar' + end + end + + it "recursively matches directories with '**/<characters>'" do + Dir.send(@method, '**/*fil?{,.}*').uniq.sort.should == + %w[deeply/nested/directory/structure/file_one + deeply/nested/directory/structure/file_one.ext + deeply/nondotfile + + dir/filename_ordering + dir_filename_ordering + + file_one.ext + file_two.ext + + nondotfile + + special/test{1}/file[1] + + subdir_one/nondotfile + subdir_two/nondotfile + subdir_two/nondotfile.ext] + end + + it "ignores matching through directories that doesn't exist" do + Dir.send(@method, "deeply/notthere/blah*/whatever").should == [] + end + + it "ignores matching only directories under an nonexistent path" do + Dir.send(@method, "deeply/notthere/blah/").should == [] + end + + platform_is_not :windows do + it "matches UTF-8 paths" do + Dir.send(@method, "special/こんにちは{,.txt}").should == ["special/こんにちは.txt"] + end + end + + context ":base option passed" do + before :each do + @mock_dir = File.expand_path tmp('dir_glob_mock') + + %w[ + a/b/x + a/b/c/y + a/b/c/d/z + ].each do |path| + file = File.join @mock_dir, path + mkdir_p File.dirname(file) + touch file + end + end + + after :each do + rm_r @mock_dir + end + + it "matches entries only from within the specified directory" do + path = File.join(@mock_dir, "a/b/c") + Dir.send(@method, "*", base: path).sort.should == %w( d y ) + end + + it "accepts both relative and absolute paths" do + require 'pathname' + + path_abs = File.join(@mock_dir, "a/b/c") + path_rel = Pathname.new(path_abs).relative_path_from(Pathname.new(Dir.pwd)) + + result_abs = Dir.send(@method, "*", base: path_abs).sort + result_rel = Dir.send(@method, "*", base: path_rel).sort + + result_abs.should == %w( d y ) + result_rel.should == %w( d y ) + end + + it "returns [] if specified path does not exist" do + path = File.join(@mock_dir, "fake-name") + File.should_not.exist?(path) + + Dir.send(@method, "*", base: path).should == [] + end + + it "returns [] if specified path is a file" do + path = File.join(@mock_dir, "a/b/x") + File.should.exist?(path) + + Dir.send(@method, "*", base: path).should == [] + end + + it "raises TypeError when cannot convert value to string" do + -> { + Dir.send(@method, "*", base: []) + }.should raise_error(TypeError) + end + + it "handles '' as current directory path" do + Dir.chdir @mock_dir do + Dir.send(@method, "*", base: "").should == %w( a ) + end + end + + it "handles nil as current directory path" do + Dir.chdir @mock_dir do + Dir.send(@method, "*", base: nil).should == %w( a ) + end + end + end +end + +describe :dir_glob_recursive, shared: true do + before :each do + @cwd = Dir.pwd + @mock_dir = File.expand_path tmp('dir_glob_mock') + + %w[ + a/x/b/y/e + a/x/b/y/b/z/e + ].each do |path| + file = File.join @mock_dir, path + mkdir_p File.dirname(file) + touch file + end + + Dir.chdir @mock_dir + end + + after :each do + Dir.chdir @cwd + rm_r @mock_dir + end + + it "matches multiple recursives" do + expected = %w[ + a/x/b/y/b/z/e + a/x/b/y/e + ] + + Dir.send(@method, 'a/**/b/**/e').uniq.sort.should == expected + end + + platform_is_not :windows do + it "ignores symlinks" do + file = File.join @mock_dir, 'b/z/e' + link = File.join @mock_dir, 'a/y' + + mkdir_p File.dirname(file) + touch file + File.symlink(File.dirname(file), link) + + expected = %w[ + a/x/b/y/b/z/e + a/x/b/y/e + ] + + Dir.send(@method, 'a/**/e').uniq.sort.should == expected + end + end +end diff --git a/spec/ruby/core/dir/shared/open.rb b/spec/ruby/core/dir/shared/open.rb new file mode 100644 index 0000000000..920845cba1 --- /dev/null +++ b/spec/ruby/core/dir/shared/open.rb @@ -0,0 +1,73 @@ +describe :dir_open, shared: true do + it "returns a Dir instance representing the specified directory" do + dir = Dir.send(@method, DirSpecs.mock_dir) + dir.should be_kind_of(Dir) + dir.close + end + + it "raises a SystemCallError if the directory does not exist" do + -> do + Dir.send @method, DirSpecs.nonexistent + end.should raise_error(SystemCallError) + end + + it "may take a block which is yielded to with the Dir instance" do + Dir.send(@method, DirSpecs.mock_dir) {|dir| dir.should be_kind_of(Dir)} + end + + it "returns the value of the block if a block is given" do + Dir.send(@method, DirSpecs.mock_dir) {|dir| :value }.should == :value + end + + it "closes the Dir instance when the block exits if given a block" do + closed_dir = Dir.send(@method, DirSpecs.mock_dir) { |dir| dir } + -> { closed_dir.read }.should raise_error(IOError) + end + + it "closes the Dir instance when the block exits the block even due to an exception" do + closed_dir = nil + + -> do + Dir.send(@method, DirSpecs.mock_dir) do |dir| + closed_dir = dir + raise "dir specs" + end + end.should raise_error(RuntimeError, "dir specs") + + -> { closed_dir.read }.should raise_error(IOError) + end + + it "calls #to_path on non-String arguments" do + p = mock('path') + p.should_receive(:to_path).and_return(DirSpecs.mock_dir) + Dir.send(@method, p) { true } + end + + it "accepts an options Hash" do + dir = Dir.send(@method, DirSpecs.mock_dir, encoding: "utf-8") {|d| d } + dir.should be_kind_of(Dir) + end + + it "calls #to_hash to convert the options object" do + options = mock("dir_open") + options.should_receive(:to_hash).and_return({ encoding: Encoding::UTF_8 }) + + dir = Dir.send(@method, DirSpecs.mock_dir, **options) {|d| d } + dir.should be_kind_of(Dir) + end + + it "ignores the :encoding option if it is nil" do + dir = Dir.send(@method, DirSpecs.mock_dir, encoding: nil) {|d| d } + dir.should be_kind_of(Dir) + end + + platform_is_not :windows do + it 'sets the close-on-exec flag for the directory file descriptor' do + Dir.send(@method, DirSpecs.mock_dir) do |dir| + io = IO.for_fd(dir.fileno) + io.autoclose = false + io.should.close_on_exec? + end + end + end +end diff --git a/spec/ruby/core/dir/shared/path.rb b/spec/ruby/core/dir/shared/path.rb new file mode 100644 index 0000000000..494dcca775 --- /dev/null +++ b/spec/ruby/core/dir/shared/path.rb @@ -0,0 +1,30 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/common' +require_relative 'closed' + +describe :dir_path, shared: true do + it "returns the path that was supplied to .new or .open" do + dir = Dir.open DirSpecs.mock_dir + begin + dir.send(@method).should == DirSpecs.mock_dir + ensure + dir.close rescue nil + end + end + + it "returns the path even when called on a closed Dir instance" do + dir = Dir.open DirSpecs.mock_dir + dir.close + dir.send(@method).should == DirSpecs.mock_dir + end + + it "returns a String with the same encoding as the argument to .open" do + path = DirSpecs.mock_dir.force_encoding Encoding::IBM866 + dir = Dir.open path + begin + dir.send(@method).encoding.should equal(Encoding::IBM866) + ensure + dir.close + end + end +end diff --git a/spec/ruby/core/dir/shared/pos.rb b/spec/ruby/core/dir/shared/pos.rb new file mode 100644 index 0000000000..2165932d99 --- /dev/null +++ b/spec/ruby/core/dir/shared/pos.rb @@ -0,0 +1,51 @@ +describe :dir_pos, shared: true do + before :each do + @dir = Dir.open DirSpecs.mock_dir + end + + after :each do + @dir.close rescue nil + end + + it "returns an Integer representing the current position in the directory" do + @dir.send(@method).should be_kind_of(Integer) + @dir.send(@method).should be_kind_of(Integer) + @dir.send(@method).should be_kind_of(Integer) + end + + it "returns a different Integer if moved from previous position" do + a = @dir.send(@method) + @dir.read + b = @dir.send(@method) + + a.should be_kind_of(Integer) + b.should be_kind_of(Integer) + + a.should_not == b + end +end + +describe :dir_pos_set, shared: true do + before :each do + @dir = Dir.open DirSpecs.mock_dir + end + + after :each do + @dir.close + end + + # NOTE: #seek/#pos= to a position not returned by #tell/#pos is undefined + # and should not be spec'd. + + it "moves the read position to a previously obtained position" do + pos = @dir.pos + a = @dir.read + b = @dir.read + @dir.send @method, pos + c = @dir.read + + a.should_not == b + b.should_not == c + c.should == a + end +end diff --git a/spec/ruby/core/dir/shared/pwd.rb b/spec/ruby/core/dir/shared/pwd.rb new file mode 100644 index 0000000000..2a8d7fe790 --- /dev/null +++ b/spec/ruby/core/dir/shared/pwd.rb @@ -0,0 +1,45 @@ +describe :dir_pwd, shared: true do + before :each do + @fs_encoding = Encoding.find('filesystem') + end + + it "returns the current working directory" do + pwd = Dir.send(@method) + + File.directory?(pwd).should == true + + # On ubuntu gutsy, for example, /bin/pwd does not + # understand -P. With just `pwd -P`, /bin/pwd is run. + + # The following uses inode rather than file names to account for + # case insensitive file systems like default OS/X file systems + platform_is_not :windows do + File.stat(pwd).ino.should == File.stat(`/bin/sh -c "pwd -P"`.chomp).ino + end + platform_is :windows do + File.stat(pwd).ino.should == File.stat(File.expand_path(`cd`.chomp)).ino + end + end + + it "returns an absolute path" do + pwd = Dir.send(@method) + pwd.should == File.expand_path(pwd) + end + + it "returns an absolute path even when chdir to a relative path" do + Dir.chdir(".") do + pwd = Dir.send(@method) + File.directory?(pwd).should == true + pwd.should == File.expand_path(pwd) + end + end + + it "returns a String with the filesystem encoding" do + enc = Dir.send(@method).encoding + if @fs_encoding == Encoding::US_ASCII + [Encoding::US_ASCII, Encoding::BINARY].should include(enc) + else + enc.should equal(@fs_encoding) + end + end +end diff --git a/spec/ruby/core/dir/tell_spec.rb b/spec/ruby/core/dir/tell_spec.rb new file mode 100644 index 0000000000..af86dc1598 --- /dev/null +++ b/spec/ruby/core/dir/tell_spec.rb @@ -0,0 +1,18 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' +require_relative 'shared/closed' +require_relative 'shared/pos' + +describe "Dir#tell" do + before :all do + DirSpecs.create_mock_dirs + end + + after :all do + DirSpecs.delete_mock_dirs + end + + it_behaves_like :dir_pos, :tell + + it_behaves_like :dir_closed, :tell +end diff --git a/spec/ruby/core/dir/to_path_spec.rb b/spec/ruby/core/dir/to_path_spec.rb new file mode 100644 index 0000000000..77404a3dc8 --- /dev/null +++ b/spec/ruby/core/dir/to_path_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' +require_relative 'shared/path' + +describe "Dir#to_path" do + before :all do + DirSpecs.create_mock_dirs + end + + after :all do + DirSpecs.delete_mock_dirs + end + + it_behaves_like :dir_path, :to_path +end diff --git a/spec/ruby/core/dir/unlink_spec.rb b/spec/ruby/core/dir/unlink_spec.rb new file mode 100644 index 0000000000..79027e020c --- /dev/null +++ b/spec/ruby/core/dir/unlink_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/common' +require_relative 'shared/delete' + +describe "Dir.unlink" do + before :all do + DirSpecs.create_mock_dirs + end + + after :all do + DirSpecs.delete_mock_dirs + end + + it_behaves_like :dir_delete, :unlink +end |
