diff options
Diffstat (limited to 'test/fileutils')
| -rw-r--r-- | test/fileutils/clobber.rb | 89 | ||||
| -rw-r--r-- | test/fileutils/fileasserts.rb | 129 | ||||
| -rw-r--r-- | test/fileutils/test_dryrun.rb | 21 | ||||
| -rw-r--r-- | test/fileutils/test_fileutils.rb | 1287 | ||||
| -rw-r--r-- | test/fileutils/test_nowrite.rb | 91 | ||||
| -rw-r--r-- | test/fileutils/test_verbose.rb | 19 | ||||
| -rw-r--r-- | test/fileutils/visibility_tests.rb | 39 |
7 files changed, 1343 insertions, 332 deletions
diff --git a/test/fileutils/clobber.rb b/test/fileutils/clobber.rb new file mode 100644 index 0000000000..ac5c8c79c0 --- /dev/null +++ b/test/fileutils/clobber.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true +require 'fileutils' +require 'test/unit' +require 'tmpdir' +require_relative 'fileasserts' + +module TestFileUtilsClobber + include Test::Unit::FileAssertions + + def my_rm_rf(path) + if File.exist?('/bin/rm') + system %Q[/bin/rm -rf "#{path}"] + else + FileUtils.rm_rf path + end + end + + SRC = 'data/src' + COPY = 'data/copy' + + def setup + @prevdir = Dir.pwd + class << (@fileutils_output = "") + alias puts << + end + tmproot = "#{Dir.tmpdir}/fileutils.rb.#{$$}" + Dir.mkdir tmproot unless File.directory?(tmproot) + Dir.chdir tmproot + my_rm_rf 'data'; Dir.mkdir 'data' + my_rm_rf 'tmp'; Dir.mkdir 'tmp' + File.open(SRC, 'w') {|f| f.puts 'dummy' } + File.open(COPY, 'w') {|f| f.puts 'dummy' } + end + + def teardown + tmproot = Dir.pwd + Dir.chdir @prevdir + my_rm_rf tmproot + end + + def test_cp + cp SRC, 'tmp/cp' + check 'tmp/cp' + end + + def test_mv + mv SRC, 'tmp/mv' + check 'tmp/mv' + end + + def check(dest, message=nil) + assert_file_not_exist dest, message + assert_file_exist SRC, message + assert_same_file SRC, COPY, message + end + + def test_rm + rm SRC + assert_file_exist SRC + assert_same_file SRC, COPY + end + + def test_rm_f + rm_f SRC + assert_file_exist SRC + assert_same_file SRC, COPY + end + + def test_rm_rf + rm_rf SRC + assert_file_exist SRC + assert_same_file SRC, COPY + end + + def test_mkdir + mkdir 'dir' + assert_file_not_exist 'dir' + end + + def test_mkdir_p + mkdir 'dir/dir/dir' + assert_file_not_exist 'dir' + end + + def test_copy_entry + copy_entry SRC, 'tmp/copy_entry' + check 'tmp/copy_entry', bug4331 = '[ruby-dev:43129]' + end +end diff --git a/test/fileutils/fileasserts.rb b/test/fileutils/fileasserts.rb index c2e9244a96..8ab30b032c 100644 --- a/test/fileutils/fileasserts.rb +++ b/test/fileutils/fileasserts.rb @@ -1,67 +1,112 @@ +# frozen_string_literal: true # $Id$ module Test module Unit - module Assertions # redefine - - def assert_same_file(from, to) - _wrap_assertion { - assert_block("file #{from} != #{to}") { - File.read(from) == File.read(to) - } - } + module FileAssertions + def assert_same_file(from, to, message=nil) + assert_equal(File.read(from), File.read(to), "file #{from} != #{to}#{message&&': '}#{message||''}") end - def assert_same_entry(from, to) + def assert_same_entry(from, to, message=nil) a = File.stat(from) b = File.stat(to) - assert_equal a.mode, b.mode, "mode #{a.mode} != #{b.mode}" + msg = "#{message&&': '}#{message||''}" + assert_equal a.mode, b.mode, "mode #{a.mode} != #{b.mode}#{msg}" #assert_equal a.atime, b.atime - assert_equal a.mtime, b.mtime, "mtime #{a.mtime} != #{b.mtime}" - assert_equal a.uid, b.uid, "uid #{a.uid} != #{b.uid}" - assert_equal a.gid, b.gid, "gid #{a.gid} != #{b.gid}" + assert_equal_timestamp a.mtime, b.mtime, "mtime #{a.mtime} != #{b.mtime}#{msg}" + assert_equal a.uid, b.uid, "uid #{a.uid} != #{b.uid}#{msg}" + assert_equal a.gid, b.gid, "gid #{a.gid} != #{b.gid}#{msg}" end - def assert_file_exist(path) - _wrap_assertion { - assert_block("file not exist: #{path}") { - File.exist?(path) - } - } + def assert_file_exist(path, message=nil) + assert(File.exist?(path), "file not exist: #{path}#{message&&': '}#{message||''}") end - def assert_file_not_exist(path) - _wrap_assertion { - assert_block("file not exist: #{path}") { - not File.exist?(path) - } - } + def assert_file_not_exist(path, message=nil) + assert(!File.exist?(path), "file exist: #{path}#{message&&': '}#{message||''}") end - def assert_directory(path) - _wrap_assertion { - assert_block("is not directory: #{path}") { - File.directory?(path) - } - } + def assert_directory(path, message=nil) + assert(File.directory?(path), "is not directory: #{path}#{message&&': '}#{message||''}") end - def assert_symlink(path) - _wrap_assertion { - assert_block("is not a symlink: #{path}") { - File.symlink?(path) - } - } + def assert_symlink(path, message=nil) + assert(File.symlink?(path), "is not a symlink: #{path}#{message&&': '}#{message||''}") + end + + def assert_not_symlink(path, message=nil) + assert(!File.symlink?(path), "is a symlink: #{path}#{message&&': '}#{message||''}") end - def assert_not_symlink(path) - _wrap_assertion { - assert_block("is a symlink: #{path}") { - not File.symlink?(path) - } + def assert_equal_time(expected, actual, message=nil) + expected_str = expected.to_s + actual_str = actual.to_s + if expected_str == actual_str + expected_str << " (nsec=#{expected.nsec})" + actual_str << " (nsec=#{actual.nsec})" + end + full_message = build_message(message, <<EOT) +<#{expected_str}> expected but was +<#{actual_str}>. +EOT + assert_equal(expected, actual, full_message) + end + + def assert_equal_timestamp(expected, actual, message=nil) + expected_str = expected.to_s + actual_str = actual.to_s + if expected_str == actual_str + expected_str << " (nsec=#{expected.nsec})" + actual_str << " (nsec=#{actual.nsec})" + end + full_message = build_message(message, <<EOT) +<#{expected_str}> expected but was +<#{actual_str}>. +EOT + # subsecond timestamp is not portable. + assert_equal(expected.tv_sec, actual.tv_sec, full_message) + end + + def assert_filemode(expected, file, message=nil, mask: 07777) + width = ('%o' % mask).size + actual = File.stat(file).mode & mask + assert expected == actual, <<EOT +File mode of "#{file}" unexpected: + Expected: <#{'%0*o' % [width, expected]}> + Actual: <#{'%0*o' % [width, actual]}> +EOT + end + + def assert_equal_filemode(file1, file2, message=nil, mask: 07777) + mode1, mode2 = [file1, file2].map { |file| + File.stat(file).mode & mask } + width = ('%o' % mask).size + assert mode1 == mode2, <<EOT +File modes expected to be equal: + <#{'%0*o' % [width, mode1]}>: "#{file1}" + <#{'%0*o' % [width, mode2]}>: "#{file2}" +EOT + end + + def assert_ownership_group(expected, file) + actual = File.stat(file).gid + assert expected == actual, <<EOT +File group ownership of "#{file}" unexpected: + Expected: <#{expected}> + Actual: <#{actual}> +EOT end + def assert_ownership_user(expected, file) + actual = File.stat(file).uid + assert expected == actual, <<EOT +File user ownership of "#{file}" unexpected: + Expected: <#{expected}> + Actual: <#{actual}> +EOT + end end end end diff --git a/test/fileutils/test_dryrun.rb b/test/fileutils/test_dryrun.rb index 2fdd65d2f6..a3efbb4bdf 100644 --- a/test/fileutils/test_dryrun.rb +++ b/test/fileutils/test_dryrun.rb @@ -1,25 +1,18 @@ +# frozen_string_literal: true # $Id$ -require 'test/unit' require 'fileutils' +require 'test/unit' +require_relative 'visibility_tests' class TestFileUtilsDryRun < Test::Unit::TestCase include FileUtils::DryRun + include TestFileUtilsIncVisibility - def test_visibility - FileUtils::METHODS.each do |m| - assert_equal true, FileUtils::DryRun.respond_to?(m, true), - "FileUtils::DryRun.#{m} not defined" - assert_equal true, FileUtils::DryRun.respond_to?(m, false), - "FileUtils::DryRun.#{m} not public" - end - FileUtils::METHODS.each do |m| - assert_equal true, respond_to?(m, true) - "FileUtils::DryRun\##{m} is not defined" - assert_equal true, FileUtils::DryRun.private_method_defined?(m), - "FileUtils::DryRun\##{m} is not private" - end + def setup + super + @fu_module = FileUtils::DryRun end end diff --git a/test/fileutils/test_fileutils.rb b/test/fileutils/test_fileutils.rb index cab51c9e8c..92308d9557 100644 --- a/test/fileutils/test_fileutils.rb +++ b/test/fileutils/test_fileutils.rb @@ -1,88 +1,175 @@ +# frozen_string_literal: true # $Id$ require 'fileutils' -require 'fileasserts' +require 'etc' +require_relative 'fileasserts' require 'pathname' require 'tmpdir' +require 'stringio' require 'test/unit' class TestFileUtils < Test::Unit::TestCase - TMPROOT = "#{Dir.tmpdir}/fileutils.rb.#{$$}" -end + include Test::Unit::FileAssertions + + def assert_output_lines(expected, fu = self, message=nil) + old = fu.instance_variables.include?(:@fileutils_output) && fu.instance_variable_get(:@fileutils_output) + IO.pipe {|read, write| + fu.instance_variable_set(:@fileutils_output, write) + th = Thread.new { read.read } + th2 = Thread.new { + begin + yield + ensure + write.close + end + } + th_value, _ = assert_join_threads([th, th2]) + lines = th_value.lines.map {|l| l.chomp } + assert_equal(expected, lines) + } + ensure + fu.instance_variable_set(:@fileutils_output, old) if old + end -prevdir = Dir.pwd -tmproot = TestFileUtils::TMPROOT -Dir.mkdir tmproot unless File.directory?(tmproot) -Dir.chdir tmproot + m = Module.new do + def have_drive_letter? + /mswin(?!ce)|mingw|bcc|emx/ =~ RUBY_PLATFORM + end -def have_drive_letter? - /djgpp|mswin(?!ce)|mingw|bcc|emx/ =~ RUBY_PLATFORM -end + def have_file_perm? + /mswin|mingw|bcc|emx/ !~ RUBY_PLATFORM + end -def have_file_perm? - /djgpp|mswin|mingw|bcc|wince|emx/ !~ RUBY_PLATFORM -end + @@have_symlink = nil -$fileutils_rb_have_symlink = nil + def have_symlink? + if @@have_symlink == nil + @@have_symlink = check_have_symlink? + end + @@have_symlink + end -def have_symlink? - if $fileutils_rb_have_symlink == nil - $fileutils_rb_have_symlink = check_have_symlink? - end - $fileutils_rb_have_symlink -end + def check_have_symlink? + Dir.mktmpdir do |dir| + Dir.chdir(dir) do + File.symlink "symlink", "symlink" + end + end + rescue NotImplementedError, Errno::EACCES + return false + rescue + return true + end -def check_have_symlink? - File.symlink nil, nil -rescue NotImplementedError - return false -rescue - return true -end + @@have_hardlink = nil -$fileutils_rb_have_hardlink = nil + def have_hardlink? + if @@have_hardlink == nil + @@have_hardlink = check_have_hardlink? + end + @@have_hardlink + end -def have_hardlink? - if $fileutils_rb_have_hardlink == nil - $fileutils_rb_have_hardlink = check_have_hardlink? - end - $fileutils_rb_have_hardlink -end + def check_have_hardlink? + Dir.mktmpdir do |dir| + Dir.chdir(dir) do + File.write "dummy", "dummy" + File.link "dummy", "hardlink" + end + end + rescue NotImplementedError, Errno::EACCES + return false + rescue + return true + end -def check_have_hardlink? - File.link nil, nil -rescue NotImplementedError - return false -rescue - return true -end + @@no_broken_symlink = false + if /cygwin/ =~ RUBY_PLATFORM and /\bwinsymlinks:native(?:strict)?\b/ =~ ENV["CYGWIN"] + @@no_broken_symlink = true + end -begin - Dir.mkdir("\n") - Dir.rmdir("\n") - def lf_in_path_allowed? - true - end -rescue - def lf_in_path_allowed? - false - end -end + def no_broken_symlink? + @@no_broken_symlink + end + + def has_capsh? + !!system('capsh', '--print', out: File::NULL, err: File::NULL) + end + + def has_root_file_capabilities? + !!system( + 'capsh', '--has-p=CAP_DAC_OVERRIDE', '--has-p=CAP_CHOWN', '--has-p=CAP_FOWNER', + out: File::NULL, err: File::NULL + ) + end + + def root_in_posix? + if /cygwin/ =~ RUBY_PLATFORM + # FIXME: privilege if groups include root user? + return Process.groups.include?(0) + elsif has_capsh? + return has_root_file_capabilities? + elsif Process.respond_to?('uid') + return Process.uid == 0 + else + return false + end + end + + def distinct_uids(n = 2) + return unless user = Etc.getpwent + uids = [user.uid] + while user = Etc.getpwent + uid = user.uid + unless uids.include?(uid) + uids << uid + break if uids.size >= n + end + end + uids + ensure + Etc.endpwent + end -Dir.chdir prevdir -Dir.rmdir tmproot + begin + tmproot = Dir.mktmpdir "fileutils" + Dir.chdir tmproot do + Dir.mkdir("\n") + Dir.rmdir("\n") + end + def lf_in_path_allowed? + true + end + rescue + def lf_in_path_allowed? + false + end + ensure + begin + Dir.rmdir tmproot + rescue + STDERR.puts $!.inspect + STDERR.puts Dir.entries(tmproot).inspect + end + end + end + include m + extend m -class TestFileUtils + UID_1, UID_2 = distinct_uids(2) include FileUtils def check_singleton(name) - assert_equal true, ::FileUtils.public_methods.include?(name.to_s) + assert_respond_to ::FileUtils, name end def my_rm_rf(path) if File.exist?('/bin/rm') - system %Q[/bin/rm -rf "#{path}"] + system "/bin/rm", "-rf", path + elsif /mswin|mingw/ =~ RUBY_PLATFORM + system "rmdir", "/q/s", path.gsub('/', '\\'), err: IO::NULL else FileUtils.rm_rf path end @@ -95,8 +182,8 @@ class TestFileUtils def setup @prevdir = Dir.pwd - tmproot = TMPROOT - mymkdir tmproot unless File.directory?(tmproot) + @groups = [Process.gid] | Process.groups if have_file_perm? + tmproot = @tmproot = Dir.mktmpdir "fileutils" Dir.chdir tmproot my_rm_rf 'data'; mymkdir 'data' my_rm_rf 'tmp'; mymkdir 'tmp' @@ -104,9 +191,8 @@ class TestFileUtils end def teardown - tmproot = Dir.pwd Dir.chdir @prevdir - my_rm_rf tmproot + my_rm_rf @tmproot end @@ -167,23 +253,28 @@ class TestFileUtils # Test Cases # + def test_assert_output_lines + assert_raise(Test::Unit::AssertionFailedError) { + Timeout.timeout(0.5) { + assert_output_lines([]) { + Thread.current.report_on_exception = false + raise "ok" + } + } + } + end + def test_pwd check_singleton :pwd assert_equal Dir.pwd, pwd() cwd = Dir.pwd -if have_drive_letter? - cd('C:/') { - assert_equal 'C:/', pwd() + root = have_drive_letter? ? 'C:/' : '/' + cd(root) { + assert_equal root, pwd() } assert_equal cwd, pwd() -else - cd('/') { - assert_equal '/', pwd() - } - assert_equal cwd, pwd() -end end def test_cmp @@ -192,7 +283,7 @@ end TARGETS.each do |fname| assert cmp(fname, fname), 'not same?' end - assert_raises(ArgumentError) { + assert_raise(ArgumentError) { cmp TARGETS[0], TARGETS[0], :undefinedoption => true } @@ -223,28 +314,60 @@ end assert_same_entry srcpath, destpath end + assert_raise(Errno::ENOENT) { + cp 'tmp/cptmp', 'tmp/cptmp_new' + } + assert_file_not_exist('tmp/cptmp_new') + # src==dest (1) same path touch 'tmp/cptmp' - assert_raises(ArgumentError) { + assert_raise(ArgumentError) { cp 'tmp/cptmp', 'tmp/cptmp' } -if have_symlink? + end + + def test_cp_preserve_permissions + bug4507 = '[ruby-core:35518]' + touch 'tmp/cptmp' + chmod 0o755, 'tmp/cptmp' + cp 'tmp/cptmp', 'tmp/cptmp2' + + assert_equal_filemode('tmp/cptmp', 'tmp/cptmp2', bug4507, mask: ~File.umask) + end + + def test_cp_preserve_permissions_dir + bug7246 = '[ruby-core:48603]' + mkdir 'tmp/cptmp' + mkdir 'tmp/cptmp/d1' + chmod 0o745, 'tmp/cptmp/d1' + mkdir 'tmp/cptmp/d2' + chmod 0o700, 'tmp/cptmp/d2' + cp_r 'tmp/cptmp', 'tmp/cptmp2', :preserve => true + assert_equal_filemode('tmp/cptmp/d1', 'tmp/cptmp2/d1', bug7246) + assert_equal_filemode('tmp/cptmp/d2', 'tmp/cptmp2/d2', bug7246) + end + + def test_cp_symlink + touch 'tmp/cptmp' # src==dest (2) symlink and its target File.symlink 'cptmp', 'tmp/cptmp_symlink' - assert_raises(ArgumentError) { + assert_raise(ArgumentError) { cp 'tmp/cptmp', 'tmp/cptmp_symlink' } - assert_raises(ArgumentError) { + assert_raise(ArgumentError) { cp 'tmp/cptmp_symlink', 'tmp/cptmp' } + return if no_broken_symlink? # src==dest (3) looped symlink File.symlink 'symlink', 'tmp/symlink' - assert_raises(Errno::ELOOP) { + assert_raise(Errno::ELOOP) { cp 'tmp/symlink', 'tmp/symlink' } -end + end if have_symlink? + def test_cp_pathname # pathname + touch 'tmp/cptmp' assert_nothing_raised { cp 'tmp/cptmp', Pathname.new('tmp/tmpdest') cp Pathname.new('tmp/cptmp'), 'tmp/tmpdest' @@ -280,12 +403,38 @@ end assert_same_file 'tmp/cpr_src/b', 'tmp/cpr_dest/b' assert_same_file 'tmp/cpr_src/c', 'tmp/cpr_dest/c' assert_directory 'tmp/cpr_dest/d' + assert_raise(ArgumentError) do + cp_r 'tmp/cpr_src', './tmp/cpr_src' + end + assert_raise(ArgumentError) do + cp_r './tmp/cpr_src', 'tmp/cpr_src' + end + assert_raise(ArgumentError) do + cp_r './tmp/cpr_src', File.expand_path('tmp/cpr_src') + end + my_rm_rf 'tmp/cpr_src' my_rm_rf 'tmp/cpr_dest' -if have_symlink? + bug3588 = '[ruby-core:31360]' + assert_nothing_raised(ArgumentError, bug3588) do + cp_r 'tmp', 'tmp2' + end + assert_directory 'tmp2/tmp' + assert_raise(ArgumentError, bug3588) do + cp_r 'tmp2', 'tmp2/new_tmp2' + end + + bug12892 = '[ruby-core:77885] [Bug #12892]' + assert_raise(Errno::ENOENT, bug12892) do + cp_r 'non/existent', 'tmp' + end + end + + def test_cp_r_symlink # symlink in a directory mkdir 'tmp/cpr_src' + touch 'tmp/cpr_src/SLdest' ln_s 'SLdest', 'tmp/cpr_src/symlink' cp_r 'tmp/cpr_src', 'tmp/cpr_dest' assert_symlink 'tmp/cpr_dest/symlink' @@ -295,15 +444,58 @@ if have_symlink? ln_s 'cpr_src', 'tmp/cpr_src2' cp_r 'tmp/cpr_src2', 'tmp/cpr_dest2' assert_directory 'tmp/cpr_dest2' - #assert_not_symlink 'tmp/cpr_dest2' - # * 2005-05-26: feature change on trunk - #assert_symlink 'tmp/cpr_dest2' - # * 2005-09-19: revert for 1.8 (:dereference_root => true by default) assert_not_symlink 'tmp/cpr_dest2' assert_symlink 'tmp/cpr_dest2/symlink' assert_equal 'SLdest', File.readlink('tmp/cpr_dest2/symlink') -end + end if have_symlink? + + def test_cp_r_symlink_preserve + mkdir 'tmp/cross' + mkdir 'tmp/cross/a' + mkdir 'tmp/cross/b' + touch 'tmp/cross/a/f' + touch 'tmp/cross/b/f' + ln_s '../a/f', 'tmp/cross/b/l' + ln_s '../b/f', 'tmp/cross/a/l' + assert_nothing_raised { + cp_r 'tmp/cross', 'tmp/cross2', :preserve => true + } + end if have_symlink? and !no_broken_symlink? + + def test_cp_r_fifo + Dir.mkdir('tmp/cpr_src') + File.mkfifo 'tmp/cpr_src/fifo', 0600 + cp_r 'tmp/cpr_src', 'tmp/cpr_dest' + assert_equal(true, File.pipe?('tmp/cpr_dest/fifo')) + end if File.respond_to?(:mkfifo) + + def test_cp_r_dev + devs = Dir['/dev/*'] + chardev = devs.find{|f| File.chardev?(f)} + blockdev = devs.find{|f| File.blockdev?(f)} + Dir.mkdir('tmp/cpr_dest') + assert_raise(RuntimeError) { cp_r chardev, 'tmp/cpr_dest/cd' } if chardev + assert_raise(RuntimeError) { cp_r blockdev, 'tmp/cpr_dest/bd' } if blockdev + end + + begin + require 'socket' + rescue LoadError + else + def test_cp_r_socket + pend "Skipping socket test on JRuby" if RUBY_ENGINE == 'jruby' + + Dir.mkdir('tmp/cpr_src') + UNIXServer.new('tmp/cpr_src/socket').close + cp_r 'tmp/cpr_src', 'tmp/cpr_dest' + assert_equal(true, File.socket?('tmp/cpr_dest/socket')) + rescue Errno::EINVAL => error + # On some platforms (windows) sockets cannot be copied by FileUtils. + omit error.message + end if defined?(UNIXServer) + end + def test_cp_r_pathname # pathname touch 'tmp/cprtmp' assert_nothing_raised { @@ -313,6 +505,54 @@ end } end + def test_cp_r_symlink_remove_destination + Dir.mkdir 'tmp/src' + Dir.mkdir 'tmp/dest' + Dir.mkdir 'tmp/src/dir' + File.symlink 'tmp/src/dir', 'tmp/src/a' + cp_r 'tmp/src', 'tmp/dest/', remove_destination: true + cp_r 'tmp/src', 'tmp/dest/', remove_destination: true + end if have_symlink? + + def test_cp_lr + check_singleton :cp_lr + + cp_lr 'data', 'tmp' + TARGETS.each do |fname| + assert_same_file fname, "tmp/#{fname}" + end + + # a/* -> b/* + mkdir 'tmp/cpr_src' + mkdir 'tmp/cpr_dest' + File.open('tmp/cpr_src/a', 'w') {|f| f.puts 'a' } + File.open('tmp/cpr_src/b', 'w') {|f| f.puts 'b' } + File.open('tmp/cpr_src/c', 'w') {|f| f.puts 'c' } + mkdir 'tmp/cpr_src/d' + cp_lr 'tmp/cpr_src/.', 'tmp/cpr_dest' + assert_same_file 'tmp/cpr_src/a', 'tmp/cpr_dest/a' + assert_same_file 'tmp/cpr_src/b', 'tmp/cpr_dest/b' + assert_same_file 'tmp/cpr_src/c', 'tmp/cpr_dest/c' + assert_directory 'tmp/cpr_dest/d' + my_rm_rf 'tmp/cpr_src' + my_rm_rf 'tmp/cpr_dest' + + bug3588 = '[ruby-core:31360]' + mkdir 'tmp2' + assert_nothing_raised(ArgumentError, bug3588) do + cp_lr 'tmp', 'tmp2' + end + assert_directory 'tmp2/tmp' + assert_raise(ArgumentError, bug3588) do + cp_lr 'tmp2', 'tmp2/new_tmp2' + end + + bug12892 = '[ruby-core:77885] [Bug #12892]' + assert_raise(Errno::ENOENT, bug12892) do + cp_lr 'non/existent', 'tmp' + end + end if have_hardlink? + def test_mv check_singleton :mv @@ -329,38 +569,51 @@ end assert_same_file fname, 'tmp/mvdest' end - # [ruby-talk:124368] mkdir 'tmp/tmpdir' mkdir_p 'tmp/dest2/tmpdir' - assert_raises(Errno::EEXIST) { + assert_raise_with_message(Errno::EEXIST, %r' - tmp/dest2/tmpdir\z', + '[ruby-core:68706] [Bug #11021]') { mv 'tmp/tmpdir', 'tmp/dest2' } mkdir 'tmp/dest2/tmpdir/junk' - assert_raises(Errno::EEXIST) { + assert_raise(Errno::EEXIST, "[ruby-talk:124368]") { mv 'tmp/tmpdir', 'tmp/dest2' } # src==dest (1) same path touch 'tmp/cptmp' - assert_raises(ArgumentError) { + assert_raise(ArgumentError) { mv 'tmp/cptmp', 'tmp/cptmp' } -if have_symlink? + end + + def test_mv_symlink + touch 'tmp/cptmp' # src==dest (2) symlink and its target File.symlink 'cptmp', 'tmp/cptmp_symlink' - assert_raises(ArgumentError) { + assert_raise(ArgumentError) { mv 'tmp/cptmp', 'tmp/cptmp_symlink' } - assert_raises(ArgumentError) { + assert_raise(ArgumentError) { mv 'tmp/cptmp_symlink', 'tmp/cptmp' } + end if have_symlink? + + def test_mv_broken_symlink # src==dest (3) looped symlink File.symlink 'symlink', 'tmp/symlink' - assert_raises(Errno::ELOOP) { + assert_raise(Errno::ELOOP) { mv 'tmp/symlink', 'tmp/symlink' } -end + # unexist symlink + File.symlink 'xxx', 'tmp/src' + assert_nothing_raised { + mv 'tmp/src', 'tmp/dest' + } + assert_equal true, File.symlink?('tmp/dest') + end if have_symlink? and !no_broken_symlink? + def test_mv_pathname # pathname assert_nothing_raised { touch 'tmp/mvtmpsrc' @@ -402,14 +655,14 @@ end rm_f 'tmp/rmsrc' assert_file_not_exist 'tmp/rmsrc' end + end -if have_symlink? + def test_rm_symlink File.open('tmp/lnf_symlink_src', 'w') {|f| f.puts 'dummy' } - File.symlink 'tmp/lnf_symlink_src', 'tmp/lnf_symlink_dest' + File.symlink 'lnf_symlink_src', 'tmp/lnf_symlink_dest' rm_f 'tmp/lnf_symlink_dest' assert_file_not_exist 'tmp/lnf_symlink_dest' assert_file_exist 'tmp/lnf_symlink_src' -end rm_f 'notexistdatafile' rm_f 'tmp/notexistdatafile' @@ -417,7 +670,9 @@ end Dir.mkdir 'tmpdatadir' # rm_f 'tmpdatadir' Dir.rmdir 'tmpdatadir' + end if have_symlink? + def test_rm_f_2 Dir.mkdir 'tmp/tmpdir' File.open('tmp/tmpdir/a', 'w') {|f| f.puts 'dummy' } File.open('tmp/tmpdir/c', 'w') {|f| f.puts 'dummy' } @@ -425,7 +680,9 @@ end assert_file_not_exist 'tmp/tmpdir/a' assert_file_not_exist 'tmp/tmpdir/c' Dir.rmdir 'tmp/tmpdir' + end + def test_rm_pathname # pathname touch 'tmp/rmtmp1' touch 'tmp/rmtmp2' @@ -439,6 +696,11 @@ end assert_file_not_exist 'tmp/rmtmp2' assert_file_not_exist 'tmp/rmtmp3' assert_file_exist 'tmp/rmtmp4' + + # [ruby-dev:39345] + touch 'tmp/[rmtmp]' + FileUtils.rm_f 'tmp/[rmtmp]' + assert_file_not_exist 'tmp/[rmtmp]' end def test_rm_r @@ -479,16 +741,18 @@ end assert_file_not_exist 'tmp/tmpdir/a' assert_file_not_exist 'tmp/tmpdir/c' Dir.rmdir 'tmp/tmpdir' + end -if have_symlink? + def test_rm_r_symlink # [ruby-talk:94635] a symlink to the directory Dir.mkdir 'tmp/tmpdir' File.symlink '..', 'tmp/tmpdir/symlink_to_dir' rm_r 'tmp/tmpdir' assert_file_not_exist 'tmp/tmpdir' assert_file_exist 'tmp' -end + end if have_symlink? + def test_rm_r_pathname # pathname Dir.mkdir 'tmp/tmpdir1'; touch 'tmp/tmpdir1/tmp' Dir.mkdir 'tmp/tmpdir2'; touch 'tmp/tmpdir2/tmp' @@ -502,6 +766,52 @@ end assert_file_not_exist 'tmp/tmpdir3' end + def test_rm_r_no_permissions + check_singleton :rm_rf + + return if /mswin|mingw/ =~ RUBY_PLATFORM + + mkdir 'tmpdatadir' + touch 'tmpdatadir/tmpdata' + chmod "-x", 'tmpdatadir' + + begin + assert_raise Errno::EACCES do + rm_r 'tmpdatadir' + end + ensure + chmod "+x", 'tmpdatadir' + end + end + + def test_remove_entry_cjk_path + dir = "tmpdir\u3042" + my_rm_rf dir + + Dir.mkdir dir + File.write("#{dir}/\u3042.txt", "test_remove_entry_cjk_path") + + remove_entry dir + assert_file_not_exist dir + end + + def test_remove_entry_multibyte_path + c = "\u00a7" + begin + c = c.encode('filesystem') + rescue EncodingError + c = c.b + end + dir = "tmpdir#{c}" + my_rm_rf dir + + Dir.mkdir dir + File.write("#{dir}/#{c}.txt", "test_remove_entry_multibyte_path") + + remove_entry dir + assert_file_not_exist dir + end + def test_remove_entry_secure check_singleton :remove_entry_secure @@ -541,17 +851,30 @@ end remove_entry_secure 'tmp/tmpdir/c', true assert_file_not_exist 'tmp/tmpdir/a' assert_file_not_exist 'tmp/tmpdir/c' + + unless root_in_posix? + File.chmod(01777, 'tmp/tmpdir') + if File.sticky?('tmp/tmpdir') + Dir.mkdir 'tmp/tmpdir/d', 0 + assert_raise(Errno::EACCES) {remove_entry_secure 'tmp/tmpdir/d'} + File.chmod 0o777, 'tmp/tmpdir/d' + Dir.rmdir 'tmp/tmpdir/d' + end + end + Dir.rmdir 'tmp/tmpdir' + end -if have_symlink? + def test_remove_entry_secure_symlink # [ruby-talk:94635] a symlink to the directory Dir.mkdir 'tmp/tmpdir' File.symlink '..', 'tmp/tmpdir/symlink_to_dir' remove_entry_secure 'tmp/tmpdir' assert_file_not_exist 'tmp/tmpdir' assert_file_exist 'tmp' -end + end if have_symlink? + def test_remove_entry_secure_pathname # pathname Dir.mkdir 'tmp/tmpdir1'; touch 'tmp/tmpdir1/tmp' assert_nothing_raised { @@ -575,7 +898,6 @@ end assert_file_not_exist 'tmp/mvdest' end -if have_hardlink? def test_ln TARGETS.each do |fname| ln fname, 'tmp/lndest' @@ -593,18 +915,24 @@ if have_hardlink? # src==dest (1) same path touch 'tmp/cptmp' - assert_raises(Errno::EEXIST) { + assert_raise(Errno::EEXIST) { ln 'tmp/cptmp', 'tmp/cptmp' } -if have_symlink? + end if have_hardlink? + + def test_ln_symlink + touch 'tmp/cptmp' # src==dest (2) symlink and its target File.symlink 'cptmp', 'tmp/symlink' - assert_raises(Errno::EEXIST) { + assert_raise(Errno::EEXIST) { ln 'tmp/cptmp', 'tmp/symlink' # normal file -> symlink } - assert_raises(Errno::EEXIST) { + assert_raise(Errno::EEXIST) { ln 'tmp/symlink', 'tmp/cptmp' # symlink -> normal file } + end if have_symlink? + + def test_ln_broken_symlink # src==dest (3) looped symlink File.symlink 'cptmp_symlink', 'tmp/cptmp_symlink' begin @@ -612,8 +940,9 @@ if have_symlink? rescue => err assert_kind_of SystemCallError, err end -end + end if have_symlink? and !no_broken_symlink? + def test_ln_pathname # pathname touch 'tmp/lntmp' assert_nothing_raised { @@ -621,24 +950,43 @@ end ln 'tmp/lntmp', Pathname.new('tmp/lndesttmp2') ln Pathname.new('tmp/lntmp'), Pathname.new('tmp/lndesttmp3') } - end -end + end if have_hardlink? -if have_symlink? def test_ln_s check_singleton :ln_s + ln_s TARGETS, 'tmp' + each_srcdest do |fname, lnfname| + assert_equal fname, File.readlink(lnfname) + ensure + rm_f lnfname + end + + lnfname = 'symlink' + assert_raise(Errno::ENOENT, "multiple targets need a destination directory") { + ln_s TARGETS, lnfname + } + assert_file.not_exist?(lnfname) + TARGETS.each do |fname| - ln_s fname, 'tmp/lnsdest' - assert FileTest.symlink?('tmp/lnsdest'), 'not symlink' - assert_equal fname, File.readlink('tmp/lnsdest') - rm_f 'tmp/lnsdest' + fname = "../#{fname}" + lnfname = 'tmp/lnsdest' + ln_s fname, lnfname + assert_file.symlink?(lnfname) + assert_equal fname, File.readlink(lnfname) + ensure + rm_f lnfname end + end if have_symlink? and !no_broken_symlink? + + def test_ln_s_broken_symlink assert_nothing_raised { ln_s 'symlink', 'tmp/symlink' } assert_symlink 'tmp/symlink' + end if have_symlink? and !no_broken_symlink? + def test_ln_s_pathname # pathname touch 'tmp/lnsdest' assert_nothing_raised { @@ -646,24 +994,28 @@ if have_symlink? ln_s 'lnsdest', Pathname.new('tmp/symlink_tmp2') ln_s Pathname.new('lnsdest'), Pathname.new('tmp/symlink_tmp3') } - end -end + end if have_symlink? -if have_symlink? def test_ln_sf check_singleton :ln_sf TARGETS.each do |fname| + fname = "../#{fname}" ln_sf fname, 'tmp/lnsdest' assert FileTest.symlink?('tmp/lnsdest'), 'not symlink' assert_equal fname, File.readlink('tmp/lnsdest') ln_sf fname, 'tmp/lnsdest' ln_sf fname, 'tmp/lnsdest' end + end if have_symlink? + + def test_ln_sf_broken_symlink assert_nothing_raised { ln_sf 'symlink', 'tmp/symlink' } + end if have_symlink? and !no_broken_symlink? + def test_ln_sf_pathname # pathname touch 'tmp/lns_dest' assert_nothing_raised { @@ -671,8 +1023,86 @@ if have_symlink? ln_sf 'lns_dest', Pathname.new('tmp/symlink_tmp2') ln_sf Pathname.new('lns_dest'), Pathname.new('tmp/symlink_tmp3') } + end if have_symlink? + + def test_ln_sr + check_singleton :ln_sr + + assert_all_assertions_foreach(nil, *TARGETS) do |fname| + lnfname = 'tmp/lnsdest' + ln_sr fname, lnfname + assert_file.symlink?(lnfname) + assert_file.identical?(lnfname, fname) + assert_equal "../#{fname}", File.readlink(lnfname) + ensure + rm_f lnfname + end + + ln_sr TARGETS, 'tmp' + assert_all_assertions do |all| + each_srcdest do |fname, lnfname| + all.for(fname) do + assert_equal "../#{fname}", File.readlink(lnfname) + end + ensure + rm_f lnfname + end + end + + File.symlink 'data', 'link' + mkdir 'link/d1' + mkdir 'link/d2' + ln_sr 'link/d1/z', 'link/d2' + assert_equal '../d1/z', File.readlink('data/d2/z') + + mkdir 'data/src' + File.write('data/src/xxx', 'ok') + File.symlink '../data/src', 'tmp/src' + ln_sr 'tmp/src/xxx', 'data' + assert_file.symlink?('data/xxx') + assert_equal 'ok', File.read('data/xxx') + assert_equal 'src/xxx', File.readlink('data/xxx') end -end + + def test_ln_sr_not_target_directory + assert_raise(ArgumentError) { + ln_sr TARGETS, 'tmp', target_directory: false + } + assert_empty(Dir.children('tmp')) + + lnfname = 'symlink' + assert_raise(ArgumentError) { + ln_sr TARGETS, lnfname, target_directory: false + } + assert_file.not_exist?(lnfname) + + assert_all_assertions_foreach(nil, *TARGETS) do |fname| + assert_raise(Errno::EEXIST, Errno::EACCES) { + ln_sr fname, 'tmp', target_directory: false + } + dest = File.join('tmp/', File.basename(fname)) + assert_file.not_exist? dest + ln_sr fname, dest, target_directory: false + assert_file.symlink?(dest) + assert_equal("../#{fname}", File.readlink(dest)) + end + end if have_symlink? + + def test_ln_sr_broken_symlink + assert_nothing_raised { + ln_sr 'tmp/symlink', 'tmp/symlink' + } + end if have_symlink? and !no_broken_symlink? + + def test_ln_sr_pathname + # pathname + touch 'tmp/lns_dest' + assert_nothing_raised { + ln_sr Pathname.new('tmp/lns_dest'), 'tmp/symlink_tmp1' + ln_sr 'tmp/lns_dest', Pathname.new('tmp/symlink_tmp2') + ln_sr Pathname.new('tmp/lns_dest'), Pathname.new('tmp/symlink_tmp3') + } + end if have_symlink? def test_mkdir check_singleton :mkdir @@ -692,22 +1122,29 @@ end mkdir 'tmp/tmp', :mode => 0700 assert_directory 'tmp/tmp' - assert_equal 0700, (File.stat('tmp/tmp').mode & 0777) if have_file_perm? + assert_filemode 0700, 'tmp/tmp', mask: 0777 if have_file_perm? Dir.rmdir 'tmp/tmp' -if have_file_perm? + # EISDIR on OS X, FreeBSD; EEXIST on Linux; Errno::EACCES on Windows + assert_raise(Errno::EISDIR, Errno::EEXIST, Errno::EACCES) { + mkdir '/' + } + end + + def test_mkdir_file_perm mkdir 'tmp/tmp', :mode => 07777 assert_directory 'tmp/tmp' - assert_equal 07777, (File.stat('tmp/tmp').mode & 07777) + assert_filemode 07777, 'tmp/tmp' Dir.rmdir 'tmp/tmp' -end + end if have_file_perm? -if lf_in_path_allowed? + def test_mkdir_lf_in_path mkdir "tmp-first-line\ntmp-second-line" assert_directory "tmp-first-line\ntmp-second-line" Dir.rmdir "tmp-first-line\ntmp-second-line" -end + end if lf_in_path_allowed? + def test_mkdir_pathname # pathname assert_nothing_raised { mkdir Pathname.new('tmp/tmpdirtmp') @@ -754,26 +1191,56 @@ end mkdir_p 'tmp/tmp/tmp', :mode => 0700 assert_directory 'tmp/tmp' assert_directory 'tmp/tmp/tmp' - assert_equal 0700, (File.stat('tmp/tmp').mode & 0777) if have_file_perm? - assert_equal 0700, (File.stat('tmp/tmp/tmp').mode & 0777) if have_file_perm? + assert_filemode 0700, 'tmp/tmp', mask: 0777 if have_file_perm? + assert_filemode 0700, 'tmp/tmp/tmp', mask: 0777 if have_file_perm? rm_rf 'tmp/tmp' mkdir_p 'tmp/tmp', :mode => 0 assert_directory 'tmp/tmp' - assert_equal 0, (File.stat('tmp/tmp').mode & 0777) if have_file_perm? + assert_filemode 0, 'tmp/tmp', mask: 0777 if have_file_perm? # DO NOT USE rm_rf here. # (rm(1) try to chdir to parent directory, it fails to remove directory.) Dir.rmdir 'tmp/tmp' Dir.rmdir 'tmp' -if have_file_perm? + mkdir_p '/' + end + + if /mswin|mingw|cygwin/ =~ RUBY_PLATFORM + def test_mkdir_p_root + if /cygwin/ =~ RUBY_PLATFORM + tmpdir = `cygpath -ma .`.chomp + else + tmpdir = Dir.pwd + end + pend "No drive letter" unless /\A[a-z]:/i =~ tmpdir + drive = "./#{$&}" + assert_file_not_exist drive + mkdir_p "#{tmpdir}/none/dir" + assert_directory "none/dir" + assert_file_not_exist drive + ensure + Dir.rmdir(drive) if drive and File.directory?(drive) + end + + def test_mkdir_p_offline_drive + offline_drive = ("A".."Z").to_a.reverse.find {|d| !File.exist?("#{d}:/") } + + assert_raise(Errno::ENOENT) { + mkdir_p "#{offline_drive}:/new_dir" + } + end + end + + def test_mkdir_p_file_perm mkdir_p 'tmp/tmp/tmp', :mode => 07777 assert_directory 'tmp/tmp/tmp' - assert_equal 07777, (File.stat('tmp/tmp/tmp').mode & 07777) + assert_filemode 07777, 'tmp/tmp/tmp' Dir.rmdir 'tmp/tmp/tmp' Dir.rmdir 'tmp/tmp' -end + end if have_file_perm? + def test_mkdir_p_pathname # pathname assert_nothing_raised { mkdir_p Pathname.new('tmp/tmp/tmp') @@ -787,39 +1254,46 @@ end File.open('tmp/bbb', 'w') {|f| f.puts 'bbb' } install 'tmp/aaa', 'tmp/bbb', :mode => 0600 assert_equal "aaa\n", File.read('tmp/bbb') - assert_equal 0600, (File.stat('tmp/bbb').mode & 0777) if have_file_perm? + assert_filemode 0600, 'tmp/bbb', mask: 0777 if have_file_perm? t = File.mtime('tmp/bbb') install 'tmp/aaa', 'tmp/bbb' assert_equal "aaa\n", File.read('tmp/bbb') - assert_equal 0600, (File.stat('tmp/bbb').mode & 0777) if have_file_perm? - assert_equal t, File.mtime('tmp/bbb') + assert_filemode 0600, 'tmp/bbb', mask: 0777 if have_file_perm? + assert_equal_time t, File.mtime('tmp/bbb') File.unlink 'tmp/aaa' File.unlink 'tmp/bbb' # src==dest (1) same path touch 'tmp/cptmp' - assert_raises(ArgumentError) { + assert_raise(ArgumentError) { install 'tmp/cptmp', 'tmp/cptmp' } -if have_symlink? + end + + def test_install_symlink + touch 'tmp/cptmp' # src==dest (2) symlink and its target File.symlink 'cptmp', 'tmp/cptmp_symlink' - assert_raises(ArgumentError) { + assert_raise(ArgumentError) { install 'tmp/cptmp', 'tmp/cptmp_symlink' } - assert_raises(ArgumentError) { + assert_raise(ArgumentError) { install 'tmp/cptmp_symlink', 'tmp/cptmp' } + end if have_symlink? + + def test_install_broken_symlink # src==dest (3) looped symlink File.symlink 'symlink', 'tmp/symlink' - assert_raises(Errno::ELOOP) { + assert_raise(Errno::ELOOP) { # File#install invokes open(2), always ELOOP must be raised install 'tmp/symlink', 'tmp/symlink' } -end + end if have_symlink? and !no_broken_symlink? + def test_install_pathname # pathname assert_nothing_raised { rm_f 'tmp/a'; touch 'tmp/a' @@ -828,6 +1302,14 @@ end install Pathname.new('tmp/a'), 'tmp/b' rm_f 'tmp/a'; touch 'tmp/a' install Pathname.new('tmp/a'), Pathname.new('tmp/b') + my_rm_rf 'tmp/new_dir_end_with_slash' + install Pathname.new('tmp/a'), 'tmp/new_dir_end_with_slash/' + my_rm_rf 'tmp/new_dir_end_with_slash' + my_rm_rf 'tmp/new_dir' + install Pathname.new('tmp/a'), 'tmp/new_dir/a' + my_rm_rf 'tmp/new_dir' + install Pathname.new('tmp/a'), 'tmp/new_dir/new_dir_end_with_slash/' + my_rm_rf 'tmp/new_dir' rm_f 'tmp/a' touch 'tmp/a' touch 'tmp/b' @@ -839,16 +1321,116 @@ end } end -if have_file_perm? + def test_install_owner_option + File.open('tmp/aaa', 'w') {|f| f.puts 'aaa' } + File.open('tmp/bbb', 'w') {|f| f.puts 'bbb' } + assert_nothing_raised { + install 'tmp/aaa', 'tmp/bbb', :owner => "nobody", :noop => true + } + end + + def test_install_group_option + File.open('tmp/aaa', 'w') {|f| f.puts 'aaa' } + File.open('tmp/bbb', 'w') {|f| f.puts 'bbb' } + assert_nothing_raised { + install 'tmp/aaa', 'tmp/bbb', :group => "nobody", :noop => true + } + end + + def test_install_mode_option + File.open('tmp/a', 'w') {|f| f.puts 'aaa' } + install 'tmp/a', 'tmp/b', :mode => "u=wrx,g=rx,o=x" + assert_filemode 0751, 'tmp/b' + install 'tmp/b', 'tmp/c', :mode => "g+w-x" + assert_filemode 0761, 'tmp/c' + install 'tmp/c', 'tmp/d', :mode => "o+r,g=o+w,o-r,u-o" # 761 => 763 => 773 => 771 => 671 + assert_filemode 0671, 'tmp/d' + install 'tmp/d', 'tmp/e', :mode => "go=u" + assert_filemode 0666, 'tmp/e' + install 'tmp/e', 'tmp/f', :mode => "u=wrx,g=,o=" + assert_filemode 0700, 'tmp/f' + install 'tmp/f', 'tmp/g', :mode => "u=rx,go=" + assert_filemode 0500, 'tmp/g' + install 'tmp/g', 'tmp/h', :mode => "+wrx" + assert_filemode 0777, 'tmp/h' + install 'tmp/h', 'tmp/i', :mode => "u+s,o=s" + assert_filemode 04770, 'tmp/i' + install 'tmp/i', 'tmp/j', :mode => "u-w,go-wrx" + assert_filemode 04500, 'tmp/j' + install 'tmp/j', 'tmp/k', :mode => "+s" + assert_filemode 06500, 'tmp/k' + install 'tmp/a', 'tmp/l', :mode => "o+X" + assert_equal_filemode 'tmp/a', 'tmp/l' + end if have_file_perm? + def test_chmod check_singleton :chmod touch 'tmp/a' - chmod 0700, 'tmp/a' - assert_equal 0700, File.stat('tmp/a').mode & 0777 - chmod 0500, 'tmp/a' - assert_equal 0500, File.stat('tmp/a').mode & 0777 - end + chmod 0o700, 'tmp/a' + assert_filemode 0700, 'tmp/a' + chmod 0o500, 'tmp/a' + assert_filemode 0500, 'tmp/a' + end if have_file_perm? + + def test_chmod_symbol_mode + check_singleton :chmod + + touch 'tmp/a' + chmod "u=wrx,g=rx,o=x", 'tmp/a' + assert_filemode 0751, 'tmp/a' + chmod "g+w-x", 'tmp/a' + assert_filemode 0761, 'tmp/a' + chmod "o+r,g=o+w,o-r,u-o", 'tmp/a' # 761 => 763 => 773 => 771 => 671 + assert_filemode 0671, 'tmp/a' + chmod "go=u", 'tmp/a' + assert_filemode 0666, 'tmp/a' + chmod "u=wrx,g=,o=", 'tmp/a' + assert_filemode 0700, 'tmp/a' + chmod "u=rx,go=", 'tmp/a' + assert_filemode 0500, 'tmp/a' + chmod "+wrx", 'tmp/a' + assert_filemode 0777, 'tmp/a' + chmod "u+s,o=s", 'tmp/a' + assert_filemode 04770, 'tmp/a' + chmod "u-w,go-wrx", 'tmp/a' + assert_filemode 04500, 'tmp/a' + chmod "+s", 'tmp/a' + assert_filemode 06500, 'tmp/a' + + # FreeBSD ufs and tmpfs don't allow to change sticky bit against + # regular file. It's slightly strange. Anyway it's no effect bit. + # see /usr/src/sys/ufs/ufs/ufs_chmod() + # NetBSD, OpenBSD, Solaris, and AIX also deny it. + if /freebsd|netbsd|openbsd|aix/ !~ RUBY_PLATFORM + chmod "u+t,o+t", 'tmp/a' + assert_filemode 07500, 'tmp/a' + chmod "a-t,a-s", 'tmp/a' + assert_filemode 0500, 'tmp/a' + end + + assert_raise_with_message(ArgumentError, /invalid\b.*\bfile mode/) { + chmod "a", 'tmp/a' + } + + assert_raise_with_message(ArgumentError, /invalid\b.*\bfile mode/) { + chmod "x+a", 'tmp/a' + } + + assert_raise_with_message(ArgumentError, /invalid\b.*\bfile mode/) { + chmod "u+z", 'tmp/a' + } + + assert_raise_with_message(ArgumentError, /invalid\b.*\bfile mode/) { + chmod ",+x", 'tmp/a' + } + + assert_raise_with_message(ArgumentError, /invalid\b.*\bfile mode/) { + chmod "755", 'tmp/a' + } + + end if have_file_perm? + def test_chmod_R check_singleton :chmod_R @@ -856,28 +1438,282 @@ if have_file_perm? mkdir_p 'tmp/dir/dir' touch %w( tmp/dir/file tmp/dir/dir/file ) chmod_R 0700, 'tmp/dir' - assert_equal 0700, File.stat('tmp/dir').mode & 0777 - assert_equal 0700, File.stat('tmp/dir/file').mode & 0777 - assert_equal 0700, File.stat('tmp/dir/dir').mode & 0777 - assert_equal 0700, File.stat('tmp/dir/dir/file').mode & 0777 + assert_filemode 0700, 'tmp/dir', mask: 0777 + assert_filemode 0700, 'tmp/dir/file', mask: 0777 + assert_filemode 0700, 'tmp/dir/dir', mask: 0777 + assert_filemode 0700, 'tmp/dir/dir/file', mask: 0777 chmod_R 0500, 'tmp/dir' - assert_equal 0500, File.stat('tmp/dir').mode & 0777 - assert_equal 0500, File.stat('tmp/dir/file').mode & 0777 - assert_equal 0500, File.stat('tmp/dir/dir').mode & 0777 - assert_equal 0500, File.stat('tmp/dir/dir/file').mode & 0777 + assert_filemode 0500, 'tmp/dir', mask: 0777 + assert_filemode 0500, 'tmp/dir/file', mask: 0777 + assert_filemode 0500, 'tmp/dir/dir', mask: 0777 + assert_filemode 0500, 'tmp/dir/dir/file', mask: 0777 chmod_R 0700, 'tmp/dir' # to remove - end + end if have_file_perm? + + def test_chmod_symbol_mode_R + check_singleton :chmod_R + + mkdir_p 'tmp/dir/dir' + touch %w( tmp/dir/file tmp/dir/dir/file ) + chmod_R "u=wrx,g=,o=", 'tmp/dir' + assert_filemode 0700, 'tmp/dir', mask: 0777 + assert_filemode 0700, 'tmp/dir/file', mask: 0777 + assert_filemode 0700, 'tmp/dir/dir', mask: 0777 + assert_filemode 0700, 'tmp/dir/dir/file', mask: 0777 + chmod_R "u=xr,g+X,o=", 'tmp/dir' + assert_filemode 0510, 'tmp/dir', mask: 0777 + assert_filemode 0500, 'tmp/dir/file', mask: 0777 + assert_filemode 0510, 'tmp/dir/dir', mask: 0777 + assert_filemode 0500, 'tmp/dir/dir/file', mask: 0777 + chmod_R 0700, 'tmp/dir' # to remove + end if have_file_perm? + + def test_chmod_verbose + check_singleton :chmod + + assert_output_lines(["chmod 700 tmp/a", "chmod 500 tmp/a"]) { + touch 'tmp/a' + chmod 0o700, 'tmp/a', verbose: true + assert_filemode 0700, 'tmp/a', mask: 0777 + chmod 0o500, 'tmp/a', verbose: true + assert_filemode 0500, 'tmp/a', mask: 0777 + } + end if have_file_perm? + + def test_s_chmod_verbose + assert_output_lines(["chmod 700 tmp/a"], FileUtils) { + touch 'tmp/a' + FileUtils.chmod 0o700, 'tmp/a', verbose: true + assert_filemode 0700, 'tmp/a', mask: 0777 + } + end if have_file_perm? - # FIXME: How can I test this method? def test_chown check_singleton :chown - end - # FIXME: How can I test this method? - def test_chown_R - check_singleton :chown_R - end -end + return unless @groups[1] + + input_group_1 = @groups[0] + assert_output_lines([]) { + touch 'tmp/a' + # integer input for group, nil for user + chown nil, input_group_1, 'tmp/a' + assert_ownership_group @groups[0], 'tmp/a' + } + + input_group_2 = Etc.getgrgid(@groups[1]).name + assert_output_lines([]) { + touch 'tmp/b' + # string input for group, -1 for user + chown(-1, input_group_2, 'tmp/b') + assert_ownership_group @groups[1], 'tmp/b' + } + end if have_file_perm? + + def test_chown_verbose + assert_output_lines(["chown :#{@groups[0]} tmp/a1 tmp/a2"]) { + touch 'tmp/a1' + touch 'tmp/a2' + chown nil, @groups[0], ['tmp/a1', 'tmp/a2'], verbose: true + assert_ownership_group @groups[0], 'tmp/a1' + assert_ownership_group @groups[0], 'tmp/a2' + } + end if have_file_perm? + + def test_chown_noop + return unless @groups[1] + assert_output_lines([]) { + touch 'tmp/a' + chown nil, @groups[0], 'tmp/a', :noop => false + assert_ownership_group @groups[0], 'tmp/a' + chown nil, @groups[1], 'tmp/a', :noop => true + assert_ownership_group @groups[0], 'tmp/a' + chown nil, @groups[1], 'tmp/a' + assert_ownership_group @groups[1], 'tmp/a' + } + end if have_file_perm? + + if have_file_perm? + def test_chown_error + uid = UID_1 + return unless uid + + touch 'tmp/a' + + # getpwnam("") on Mac OS X doesn't err. + # passwd & group databases format is colon-separated, so user & + # group name can't contain a colon. + + assert_raise_with_message(ArgumentError, "can't find user for :::") { + chown ":::", @groups[0], 'tmp/a' + } + + assert_raise_with_message(ArgumentError, "can't find group for :::") { + chown uid, ":::", 'tmp/a' + } + + assert_raise_with_message(Errno::ENOENT, /No such file or directory/) { + chown nil, @groups[0], '' + } + end + + def test_chown_dir_group_ownership_not_recursive + return unless @groups[1] + + input_group_1 = @groups[0] + input_group_2 = @groups[1] + assert_output_lines([]) { + mkdir 'tmp/dir' + touch 'tmp/dir/a' + chown nil, input_group_1, ['tmp/dir', 'tmp/dir/a'] + assert_ownership_group @groups[0], 'tmp/dir' + assert_ownership_group @groups[0], 'tmp/dir/a' + chown nil, input_group_2, 'tmp/dir' + assert_ownership_group @groups[1], 'tmp/dir' + # Make sure FileUtils.chown does not chown recursively + assert_ownership_group @groups[0], 'tmp/dir/a' + } + end + + def test_chown_R + check_singleton :chown_R + + return unless @groups[1] + + input_group_1 = @groups[0] + input_group_2 = @groups[1] + assert_output_lines([]) { + list = ['tmp/dir', 'tmp/dir/a', 'tmp/dir/a/b', 'tmp/dir/a/b/c'] + mkdir_p 'tmp/dir/a/b/c' + touch 'tmp/d' + # string input + chown_R nil, input_group_1, 'tmp/dir' + list.each {|dir| + assert_ownership_group @groups[0], dir + } + chown_R nil, input_group_1, 'tmp/d' + assert_ownership_group @groups[0], 'tmp/d' + # list input + chown_R nil, input_group_2, ['tmp/dir', 'tmp/d'] + list += ['tmp/d'] + list.each {|dir| + assert_ownership_group @groups[1], dir + } + } + end + + def test_chown_R_verbose + assert_output_lines(["chown -R :#{@groups[0]} tmp/dir tmp/d"]) { + list = ['tmp/dir', 'tmp/dir/a', 'tmp/dir/a/b', 'tmp/dir/a/b/c'] + mkdir_p 'tmp/dir/a/b/c' + touch 'tmp/d' + chown_R nil, @groups[0], ['tmp/dir', 'tmp/d'], :verbose => true + list.each {|dir| + assert_ownership_group @groups[0], dir + } + } + end + + def test_chown_R_noop + return unless @groups[1] + + assert_output_lines([]) { + list = ['tmp/dir', 'tmp/dir/a', 'tmp/dir/a/b', 'tmp/dir/a/b/c'] + mkdir_p 'tmp/dir/a/b/c' + chown_R nil, @groups[0], 'tmp/dir', :noop => false + list.each {|dir| + assert_ownership_group @groups[0], dir + } + chown_R nil, @groups[1], 'tmp/dir', :noop => true + list.each {|dir| + assert_ownership_group @groups[0], dir + } + } + end + + def test_chown_R_force + assert_output_lines([]) { + list = ['tmp/dir', 'tmp/dir/a', 'tmp/dir/a/b', 'tmp/dir/a/b/c'] + mkdir_p 'tmp/dir/a/b/c' + assert_raise_with_message(Errno::ENOENT, /No such file or directory/) { + chown_R nil, @groups[0], ['tmp/dir', 'invalid'], :force => false + } + chown_R nil, @groups[0], ['tmp/dir', 'invalid'], :force => true + list.each {|dir| + assert_ownership_group @groups[0], dir + } + } + end + + if root_in_posix? + def test_chown_with_root + gid = @groups[0] # Most of the time, root only has one group + + files = ['tmp/a1', 'tmp/a2'] + files.each {|file| touch file} + [UID_1, UID_2].each {|uid| + assert_output_lines(["chown #{uid}:#{gid} tmp/a1 tmp/a2"]) { + chown uid, gid, files, verbose: true + files.each {|file| + assert_ownership_group gid, file + assert_ownership_user uid, file + } + } + } + end + + def test_chown_dir_user_ownership_not_recursive_with_root + assert_output_lines([]) { + mkdir 'tmp/dir' + touch 'tmp/dir/a' + chown UID_1, nil, ['tmp/dir', 'tmp/dir/a'] + assert_ownership_user UID_1, 'tmp/dir' + assert_ownership_user UID_1, 'tmp/dir/a' + chown UID_2, nil, 'tmp/dir' + assert_ownership_user UID_2, 'tmp/dir' + # Make sure FileUtils.chown does not chown recursively + assert_ownership_user UID_1, 'tmp/dir/a' + } + end + + def test_chown_R_with_root + assert_output_lines([]) { + list = ['tmp/dir', 'tmp/dir/a', 'tmp/dir/a/b', 'tmp/dir/a/b/c'] + mkdir_p 'tmp/dir/a/b/c' + touch 'tmp/d' + # string input + chown_R UID_1, nil, 'tmp/dir' + list.each {|dir| + assert_ownership_user UID_1, dir + } + chown_R UID_1, nil, 'tmp/d' + assert_ownership_user UID_1, 'tmp/d' + # list input + chown_R UID_2, nil, ['tmp/dir', 'tmp/d'] + list += ['tmp/d'] + list.each {|dir| + assert_ownership_user UID_2, dir + } + } + end + else + def test_chown_without_permission + touch 'tmp/a' + assert_raise(Errno::EPERM) { + chown UID_1, nil, 'tmp/a' + chown UID_2, nil, 'tmp/a' + } + end + + def test_chown_R_without_permission + touch 'tmp/a' + assert_raise(Errno::EPERM) { + chown_R UID_1, nil, 'tmp/a' + chown_R UID_2, nil, 'tmp/a' + } + end + end + end if UID_1 and UID_2 def test_copy_entry check_singleton :copy_entry @@ -887,8 +1723,11 @@ end assert_same_file srcpath, destpath assert_equal File.stat(srcpath).ftype, File.stat(destpath).ftype end -if have_symlink? + end + + def test_copy_entry_symlink # root is a symlink + touch 'tmp/somewhere' File.symlink 'somewhere', 'tmp/symsrc' copy_entry 'tmp/symsrc', 'tmp/symdest' assert_symlink 'tmp/symdest' @@ -896,14 +1735,22 @@ if have_symlink? # content is a symlink mkdir 'tmp/dir' + touch 'tmp/dir/somewhere' File.symlink 'somewhere', 'tmp/dir/sym' copy_entry 'tmp/dir', 'tmp/dirdest' assert_directory 'tmp/dirdest' assert_not_symlink 'tmp/dirdest' assert_symlink 'tmp/dirdest/sym' assert_equal 'somewhere', File.readlink('tmp/dirdest/sym') -end - end + end if have_symlink? + + def test_copy_entry_symlink_remove_destination + Dir.mkdir 'tmp/dir' + File.symlink 'tmp/dir', 'tmp/dest' + touch 'tmp/src' + copy_entry 'tmp/src', 'tmp/dest', false, false, true + assert_file_exist 'tmp/dest' + end if have_symlink? def test_copy_file check_singleton :copy_file @@ -918,20 +1765,21 @@ end check_singleton :copy_stream # IO each_srcdest do |srcpath, destpath| - File.open(srcpath) {|src| - File.open(destpath, 'w') {|dest| + File.open(srcpath, 'rb') {|src| + File.open(destpath, 'wb') {|dest| copy_stream src, dest } } assert_same_file srcpath, destpath end + end + def test_copy_stream_duck + check_singleton :copy_stream # duck typing test [ruby-dev:25369] - my_rm_rf 'tmp' - Dir.mkdir 'tmp' each_srcdest do |srcpath, destpath| - File.open(srcpath) {|src| - File.open(destpath, 'w') {|dest| + File.open(srcpath, 'rb') {|src| + File.open(destpath, 'wb') {|dest| copy_stream Stream.new(src), Stream.new(dest) } } @@ -944,13 +1792,14 @@ end File.open('data/tmp', 'w') {|f| f.puts 'dummy' } remove_file 'data/tmp' assert_file_not_exist 'data/tmp' -if have_file_perm? + end + + def test_remove_file_file_perm File.open('data/tmp', 'w') {|f| f.puts 'dummy' } - File.chmod 0, 'data/tmp' + File.chmod 0o000, 'data/tmp' remove_file 'data/tmp' assert_file_not_exist 'data/tmp' -end - end + end if have_file_perm? def test_remove_dir check_singleton :remove_dir @@ -958,12 +1807,21 @@ end File.open('data/tmpdir/a', 'w') {|f| f.puts 'dummy' } remove_dir 'data/tmpdir' assert_file_not_exist 'data/tmpdir' -if have_file_perm? + end + + def test_remove_dir_file_perm Dir.mkdir 'data/tmpdir' - File.chmod 0555, 'data/tmpdir' + File.chmod 0o555, 'data/tmpdir' remove_dir 'data/tmpdir' assert_file_not_exist 'data/tmpdir' -end + end if have_file_perm? + + def test_remove_dir_with_file + File.write('data/tmpfile', 'dummy') + assert_raise(Errno::ENOTDIR) { remove_dir 'data/tmpfile' } + assert_file_exist 'data/tmpfile' + ensure + File.unlink('data/tmpfile') if File.exist?('data/tmpfile') end def test_compare_file @@ -981,8 +1839,8 @@ end @f = f end - def read(n) - @f.read(n) + def read(*args) + @f.read(*args) end def write(str) @@ -1009,16 +1867,47 @@ end uptodate? 'tmp/a', ['tmp/b', Pathname.new('tmp/c')] uptodate? Pathname.new('tmp/a'), [Pathname.new('tmp/b'), Pathname.new('tmp/c')] } + # [Bug #6708] [ruby-core:46256] + assert_raise_with_message(ArgumentError, /wrong number of arguments \(.*\b3\b.* 2\)/) { + uptodate?('new',['old', 'oldest'], {}) + } end def test_cd check_singleton :cd end + def test_cd_result + assert_equal 42, cd('.') { 42 } + end + def test_chdir check_singleton :chdir end + def test_chdir_verbose + assert_output_lines(["cd .", "cd -"], FileUtils) do + FileUtils.chdir('.', verbose: true){} + end + end + + def test_chdir_verbose_frozen + o = Object.new + o.extend(FileUtils) + o.singleton_class.send(:public, :chdir) + o.freeze + orig_stdout = $stdout + $stdout = StringIO.new + o.chdir('.', verbose: true){} + $stdout.rewind + assert_equal(<<-END, $stdout.read) +cd . +cd - + END + ensure + $stdout = orig_stdout if orig_stdout + end + def test_getwd check_singleton :getwd end @@ -1045,10 +1934,54 @@ end def test_rm_rf check_singleton :rm_rf + + return if /mswin|mingw/ =~ RUBY_PLATFORM + + mkdir 'tmpdatadir' + chmod 0o000, 'tmpdatadir' + rm_rf 'tmpdatadir' + + assert_file_not_exist 'tmpdatadir' end def test_rmdir check_singleton :rmdir + + begin + Dir.rmdir '/' + rescue Errno::ENOTEMPTY + rescue => e + assert_raise(e.class) { + # Dir.rmdir('') raises Errno::ENOENT. + # FileUtils#rmdir ignores it. + # And this test failed as expected. + rmdir '/' + } + end + + subdir = 'data/sub/dir' + mkdir_p(subdir) + File.write("#{subdir}/file", '') + msg = "should fail to remove non-empty directory" + assert_raise(Errno::ENOTEMPTY, Errno::EEXIST, msg) { + rmdir(subdir) + } + assert_raise(Errno::ENOTEMPTY, Errno::EEXIST, msg) { + rmdir(subdir, parents: true) + } + File.unlink("#{subdir}/file") + assert_raise(Errno::ENOENT) { + rmdir("#{subdir}/nonexistent") + } + assert_raise(Errno::ENOENT) { + rmdir("#{subdir}/nonexistent", parents: true) + } + assert_nothing_raised(Errno::ENOENT) { + rmdir(subdir, parents: true) + } + assert_file_not_exist(subdir) + assert_file_not_exist('data/sub') + assert_directory('data') end def test_rmtree diff --git a/test/fileutils/test_nowrite.rb b/test/fileutils/test_nowrite.rb index 369f8ca608..659fa20886 100644 --- a/test/fileutils/test_nowrite.rb +++ b/test/fileutils/test_nowrite.rb @@ -1,99 +1,18 @@ +# frozen_string_literal: true # $Id$ require 'fileutils' -require 'fileasserts' -require 'tmpdir' require 'test/unit' +require_relative 'visibility_tests' class TestFileUtilsNoWrite < Test::Unit::TestCase include FileUtils::NoWrite - - def test_visibility - FileUtils::METHODS.each do |m| - assert_equal true, FileUtils::NoWrite.respond_to?(m, true), - "FileUtils::NoWrite.#{m} is not defined" - assert_equal true, FileUtils::NoWrite.respond_to?(m, false), - "FileUtils::NoWrite.#{m} is not public" - end - FileUtils::METHODS.each do |m| - assert_equal true, respond_to?(m, true), - "FileUtils::NoWrite\##{m} is not defined" - assert_equal true, FileUtils::NoWrite.private_method_defined?(m), - "FileUtils::NoWrite\##{m} is not private" - end - end - - def my_rm_rf(path) - if File.exist?('/bin/rm') - system %Q[/bin/rm -rf "#{path}"] - else - FileUtils.rm_rf path - end - end - - SRC = 'data/src' - COPY = 'data/copy' + include TestFileUtilsIncVisibility def setup - @prevdir = Dir.pwd - tmproot = "#{Dir.tmpdir}/fileutils.rb.#{$$}" - Dir.mkdir tmproot unless File.directory?(tmproot) - Dir.chdir tmproot - my_rm_rf 'data'; Dir.mkdir 'data' - my_rm_rf 'tmp'; Dir.mkdir 'tmp' - File.open(SRC, 'w') {|f| f.puts 'dummy' } - File.open(COPY, 'w') {|f| f.puts 'dummy' } - end - - def teardown - tmproot = Dir.pwd - Dir.chdir @prevdir - my_rm_rf tmproot - end - - def test_cp - cp SRC, 'tmp/cp' - check 'tmp/cp' - end - - def test_mv - mv SRC, 'tmp/mv' - check 'tmp/mv' - end - - def check(dest) - assert_file_not_exist dest - assert_file_exist SRC - assert_same_file SRC, COPY - end - - def test_rm - rm SRC - assert_file_exist SRC - assert_same_file SRC, COPY - end - - def test_rm_f - rm_f SRC - assert_file_exist SRC - assert_same_file SRC, COPY - end - - def test_rm_rf - rm_rf SRC - assert_file_exist SRC - assert_same_file SRC, COPY - end - - def test_mkdir - mkdir 'dir' - assert_file_not_exist 'dir' - end - - def test_mkdir_p - mkdir 'dir/dir/dir' - assert_file_not_exist 'dir' + super + @fu_module = FileUtils::NoWrite end end diff --git a/test/fileutils/test_verbose.rb b/test/fileutils/test_verbose.rb index e60e85ea4e..7987af2395 100644 --- a/test/fileutils/test_verbose.rb +++ b/test/fileutils/test_verbose.rb @@ -1,25 +1,18 @@ +# frozen_string_literal: true # $Id$ require 'test/unit' require 'fileutils' +require_relative 'visibility_tests' class TestFileUtilsVerbose < Test::Unit::TestCase include FileUtils::Verbose + include TestFileUtilsIncVisibility - def test_visibility - FileUtils::METHODS.each do |m| - assert_equal true, FileUtils::Verbose.respond_to?(m, true), - "FileUtils::Verbose.#{m} is not defined" - assert_equal true, FileUtils::Verbose.respond_to?(m, false), - "FileUtils::Verbose.#{m} is not public" - end - FileUtils::METHODS.each do |m| - assert_equal true, respond_to?(m, true), - "FileUtils::Verbose.#{m} is not defined" - assert_equal true, FileUtils::Verbose.private_method_defined?(m), - "FileUtils::Verbose.#{m} is not private" - end + def setup + super + @fu_module = FileUtils::Verbose end end diff --git a/test/fileutils/visibility_tests.rb b/test/fileutils/visibility_tests.rb new file mode 100644 index 0000000000..9f8f8dc9e1 --- /dev/null +++ b/test/fileutils/visibility_tests.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true +require 'test/unit' +require 'fileutils' + +## +# These tests are reused in the FileUtils::Verbose, FileUtils::NoWrite and +# FileUtils::DryRun tests + +module TestFileUtilsIncVisibility + + FileUtils::METHODS.each do |m| + define_method "test_singleton_visibility_#{m}" do + assert @fu_module.respond_to?(m, true), + "FileUtils::Verbose.#{m} is not defined" + assert @fu_module.respond_to?(m, false), + "FileUtils::Verbose.#{m} is not public" + end + + define_method "test_visibility_#{m}" do + assert respond_to?(m, true), + "FileUtils::Verbose\##{m} is not defined" + assert @fu_module.private_method_defined?(m), + "FileUtils::Verbose\##{m} is not private" + end + end + + FileUtils::StreamUtils_.private_instance_methods.each do |m| + define_method "test_singleton_visibility_#{m}" do + assert @fu_module.respond_to?(m, true), + "FileUtils::Verbose\##{m} is not defined" + end + + define_method "test_visibility_#{m}" do + assert respond_to?(m, true), + "FileUtils::Verbose\##{m} is not defined" + end + end + +end |
