1# frozen_string_literal: false
2require "test/unit"
3require "fileutils"
4require "tmpdir"
5require "socket"
6require '-test-/file'
7
8class TestFileExhaustive < Test::Unit::TestCase
9  DRIVE = Dir.pwd[%r'\A(?:[a-z]:|//[^/]+/[^/]+)'i]
10  POSIX = /cygwin|mswin|bccwin|mingw|emx/ !~ RUBY_PLATFORM
11  NTFS = !(/mingw|mswin|bccwin/ !~ RUBY_PLATFORM)
12
13  def assert_incompatible_encoding
14    d = "\u{3042}\u{3044}".encode("utf-16le")
15    assert_raise(Encoding::CompatibilityError) {yield d}
16    m = Class.new {define_method(:to_path) {d}}
17    assert_raise(Encoding::CompatibilityError) {yield m.new}
18  end
19
20  def setup
21    @dir = Dir.mktmpdir("rubytest-file")
22    File.chown(-1, Process.gid, @dir)
23  end
24
25  def teardown
26    GC.start
27    FileUtils.remove_entry_secure @dir
28  end
29
30  def make_tmp_filename(prefix)
31    "#{@dir}/#{prefix}.test"
32  end
33
34  def rootdir
35    return @rootdir if defined? @rootdir
36    @rootdir = "#{DRIVE}/"
37    @rootdir
38  end
39
40  def nofile
41    return @nofile if defined? @nofile
42    @nofile = make_tmp_filename("nofile")
43    @nofile
44  end
45
46  def make_file(content, file)
47    open(file, "w") {|fh| fh << content }
48  end
49
50  def zerofile
51    return @zerofile if defined? @zerofile
52    @zerofile = make_tmp_filename("zerofile")
53    make_file("", @zerofile)
54    @zerofile
55  end
56
57  def regular_file
58    return @file if defined? @file
59    @file = make_tmp_filename("file")
60    make_file("foo", @file)
61    @file
62  end
63
64  def utf8_file
65    return @utf8file if defined? @utf8file
66    @utf8file = make_tmp_filename("\u3066\u3059\u3068")
67    make_file("foo", @utf8file)
68    @utf8file
69  end
70
71  def notownedfile
72    return @notownedfile if defined? @notownedfile
73    if Process.euid != 0
74      @notownedfile = '/'
75    else
76      @notownedfile = nil
77    end
78    @notownedfile
79  end
80
81  def suidfile
82    return @suidfile if defined? @suidfile
83    if POSIX
84      @suidfile = make_tmp_filename("suidfile")
85      make_file("", @suidfile)
86      File.chmod 04500, @suidfile
87      @suidfile
88    else
89      @suidfile = nil
90    end
91  end
92
93  def sgidfile
94    return @sgidfile if defined? @sgidfile
95    if POSIX
96      @sgidfile = make_tmp_filename("sgidfile")
97      make_file("", @sgidfile)
98      File.chmod 02500, @sgidfile
99      @sgidfile
100    else
101      @sgidfile = nil
102    end
103  end
104
105  def stickyfile
106    return @stickyfile if defined? @stickyfile
107    if POSIX
108      @stickyfile = make_tmp_filename("stickyfile")
109      Dir.mkdir(@stickyfile)
110      File.chmod 01500, @stickyfile
111      @stickyfile
112    else
113      @stickyfile = nil
114    end
115  end
116
117  def symlinkfile
118    return @symlinkfile if defined? @symlinkfile
119    @symlinkfile = make_tmp_filename("symlinkfile")
120    begin
121      File.symlink(regular_file, @symlinkfile)
122    rescue NotImplementedError, Errno::EACCES, Errno::EPERM
123      @symlinkfile = nil
124    end
125    @symlinkfile
126  end
127
128  def hardlinkfile
129    return @hardlinkfile if defined? @hardlinkfile
130    @hardlinkfile = make_tmp_filename("hardlinkfile")
131    begin
132      File.link(regular_file, @hardlinkfile)
133    rescue NotImplementedError, Errno::EINVAL	# EINVAL for Windows Vista
134      @hardlinkfile = nil
135    end
136    @hardlinkfile
137  end
138
139  def fifo
140    return @fifo if defined? @fifo
141    if POSIX
142      fn = make_tmp_filename("fifo")
143      File.mkfifo(fn)
144      @fifo = fn
145    else
146      @fifo = nil
147    end
148    @fifo
149  end
150
151  def socket
152    return @socket if defined? @socket
153    if defined? UNIXServer
154      socket = make_tmp_filename("s")
155      UNIXServer.open(socket).close
156      @socket = socket
157    else
158      @socket = nil
159    end
160  end
161
162  def chardev
163    return @chardev if defined? @chardev
164    @chardev = File::NULL == "/dev/null" ? "/dev/null" : nil
165    @chardev
166  end
167
168  def blockdev
169    return @blockdev if defined? @blockdev
170    if /linux/ =~ RUBY_PLATFORM
171      @blockdev = %w[/dev/loop0 /dev/sda /dev/vda /dev/xvda1].find {|f| File.exist? f }
172    else
173      @blockdev = nil
174    end
175    @blockdev
176  end
177
178  def test_path
179    [regular_file, utf8_file].each do |file|
180      assert_equal(file, File.open(file) {|f| f.path})
181      assert_equal(file, File.path(file))
182      o = Object.new
183      class << o; self; end.class_eval do
184        define_method(:to_path) { file }
185      end
186      assert_equal(file, File.path(o))
187    end
188  end
189
190  def assert_integer(n)
191    assert_kind_of(Integer, n)
192  end
193
194  def assert_integer_or_nil(n)
195    msg = ->{"#{n.inspect} is neither Integer nor nil."}
196    if n
197      assert_kind_of(Integer, n, msg)
198    else
199      assert_nil(n, msg)
200    end
201  end
202
203  def test_stat
204    fn1 = regular_file
205    hardlinkfile
206    sleep(1.1)
207    fn2 = fn1 + "2"
208    make_file("foo", fn2)
209    fs1, fs2 = File.stat(fn1), File.stat(fn2)
210    assert_nothing_raised do
211      assert_equal(0, fs1 <=> fs1)
212      assert_equal(-1, fs1 <=> fs2)
213      assert_equal(1, fs2 <=> fs1)
214      assert_nil(fs1 <=> nil)
215      assert_integer(fs1.dev)
216      assert_integer_or_nil(fs1.rdev)
217      assert_integer_or_nil(fs1.dev_major)
218      assert_integer_or_nil(fs1.dev_minor)
219      assert_integer_or_nil(fs1.rdev_major)
220      assert_integer_or_nil(fs1.rdev_minor)
221      assert_integer(fs1.ino)
222      assert_integer(fs1.mode)
223      unless /emx|mswin|mingw/ =~ RUBY_PLATFORM
224        # on Windows, nlink is always 1. but this behavior will be changed
225        # in the future.
226        assert_equal(hardlinkfile ? 2 : 1, fs1.nlink)
227      end
228      assert_integer(fs1.uid)
229      assert_integer(fs1.gid)
230      assert_equal(3, fs1.size)
231      assert_integer_or_nil(fs1.blksize)
232      assert_integer_or_nil(fs1.blocks)
233      assert_kind_of(Time, fs1.atime)
234      assert_kind_of(Time, fs1.mtime)
235      assert_kind_of(Time, fs1.ctime)
236      assert_kind_of(String, fs1.inspect)
237    end
238    assert_raise(Errno::ENOENT) { File.stat(nofile) }
239    assert_kind_of(File::Stat, File.open(fn1) {|f| f.stat})
240    assert_raise(Errno::ENOENT) { File.lstat(nofile) }
241    assert_kind_of(File::Stat, File.open(fn1) {|f| f.lstat})
242  end
243
244  def test_stat_drive_root
245    assert_nothing_raised { File.stat(DRIVE + "/") }
246    assert_nothing_raised { File.stat(DRIVE + "/.") }
247    assert_nothing_raised { File.stat(DRIVE + "/..") }
248    assert_raise(Errno::ENOENT) { File.stat(DRIVE + "/...") }
249    # want to test the root of empty drive, but there is no method to test it...
250  end if DRIVE
251
252  def test_stat_dotted_prefix
253    Dir.mktmpdir do |dir|
254      prefix = File.join(dir, "...a")
255      Dir.mkdir(prefix)
256      assert_file.exist?(prefix)
257
258      assert_nothing_raised { File.stat(prefix) }
259
260      Dir.chdir(dir) do
261        assert_nothing_raised { File.stat(File.basename(prefix)) }
262      end
263    end
264  end if NTFS
265
266  def test_lstat
267    return unless symlinkfile
268    assert_equal(false, File.stat(symlinkfile).symlink?)
269    assert_equal(true, File.lstat(symlinkfile).symlink?)
270    f = File.new(symlinkfile)
271    assert_equal(false, f.stat.symlink?)
272    assert_equal(true, f.lstat.symlink?)
273    f.close
274  end
275
276  def test_directory_p
277    assert_file.directory?(@dir)
278    assert_file.not_directory?(@dir+"/...")
279    assert_file.not_directory?(regular_file)
280    assert_file.not_directory?(utf8_file)
281    assert_file.not_directory?(nofile)
282  end
283
284  def test_pipe_p
285    assert_file.not_pipe?(@dir)
286    assert_file.not_pipe?(regular_file)
287    assert_file.not_pipe?(utf8_file)
288    assert_file.not_pipe?(nofile)
289    assert_file.pipe?(fifo) if fifo
290  end
291
292  def test_symlink_p
293    assert_file.not_symlink?(@dir)
294    assert_file.not_symlink?(regular_file)
295    assert_file.not_symlink?(utf8_file)
296    assert_file.symlink?(symlinkfile) if symlinkfile
297    assert_file.not_symlink?(hardlinkfile) if hardlinkfile
298    assert_file.not_symlink?(nofile)
299  end
300
301  def test_socket_p
302    assert_file.not_socket?(@dir)
303    assert_file.not_socket?(regular_file)
304    assert_file.not_socket?(utf8_file)
305    assert_file.not_socket?(nofile)
306    assert_file.socket?(socket) if socket
307  end
308
309  def test_blockdev_p
310    assert_file.not_blockdev?(@dir)
311    assert_file.not_blockdev?(regular_file)
312    assert_file.not_blockdev?(utf8_file)
313    assert_file.not_blockdev?(nofile)
314    assert_file.blockdev?(blockdev) if blockdev
315  end
316
317  def test_chardev_p
318    assert_file.not_chardev?(@dir)
319    assert_file.not_chardev?(regular_file)
320    assert_file.not_chardev?(utf8_file)
321    assert_file.not_chardev?(nofile)
322    assert_file.chardev?(chardev) if chardev
323  end
324
325  def test_exist_p
326    assert_file.exist?(@dir)
327    assert_file.exist?(regular_file)
328    assert_file.exist?(utf8_file)
329    assert_file.not_exist?(nofile)
330  end
331
332  def test_readable_p
333    return if Process.euid == 0
334    File.chmod(0200, regular_file)
335    assert_file.not_readable?(regular_file)
336    File.chmod(0600, regular_file)
337    assert_file.readable?(regular_file)
338
339    File.chmod(0200, utf8_file)
340    assert_file.not_readable?(utf8_file)
341    File.chmod(0600, utf8_file)
342    assert_file.readable?(utf8_file)
343
344    assert_file.not_readable?(nofile)
345  end if POSIX
346
347  def test_readable_real_p
348    return if Process.euid == 0
349    File.chmod(0200, regular_file)
350    assert_file.not_readable_real?(regular_file)
351    File.chmod(0600, regular_file)
352    assert_file.readable_real?(regular_file)
353
354    File.chmod(0200, utf8_file)
355    assert_file.not_readable_real?(utf8_file)
356    File.chmod(0600, utf8_file)
357    assert_file.readable_real?(utf8_file)
358
359    assert_file.not_readable_real?(nofile)
360  end if POSIX
361
362  def test_world_readable_p
363    File.chmod(0006, regular_file)
364    assert_file.world_readable?(regular_file)
365    File.chmod(0060, regular_file)
366    assert_file.not_world_readable?(regular_file)
367    File.chmod(0600, regular_file)
368    assert_file.not_world_readable?(regular_file)
369
370    File.chmod(0006, utf8_file)
371    assert_file.world_readable?(utf8_file)
372    File.chmod(0060, utf8_file)
373    assert_file.not_world_readable?(utf8_file)
374    File.chmod(0600, utf8_file)
375    assert_file.not_world_readable?(utf8_file)
376
377    assert_file.not_world_readable?(nofile)
378  end if POSIX
379
380  def test_writable_p
381    return if Process.euid == 0
382    File.chmod(0400, regular_file)
383    assert_file.not_writable?(regular_file)
384    File.chmod(0600, regular_file)
385    assert_file.writable?(regular_file)
386
387    File.chmod(0400, utf8_file)
388    assert_file.not_writable?(utf8_file)
389    File.chmod(0600, utf8_file)
390    assert_file.writable?(utf8_file)
391
392    assert_file.not_writable?(nofile)
393  end if POSIX
394
395  def test_writable_real_p
396    return if Process.euid == 0
397    File.chmod(0400, regular_file)
398    assert_file.not_writable_real?(regular_file)
399    File.chmod(0600, regular_file)
400    assert_file.writable_real?(regular_file)
401
402    File.chmod(0400, utf8_file)
403    assert_file.not_writable_real?(utf8_file)
404    File.chmod(0600, utf8_file)
405    assert_file.writable_real?(utf8_file)
406
407    assert_file.not_writable_real?(nofile)
408  end if POSIX
409
410  def test_world_writable_p
411    File.chmod(0006, regular_file)
412    assert_file.world_writable?(regular_file)
413    File.chmod(0060, regular_file)
414    assert_file.not_world_writable?(regular_file)
415    File.chmod(0600, regular_file)
416    assert_file.not_world_writable?(regular_file)
417
418    File.chmod(0006, utf8_file)
419    assert_file.world_writable?(utf8_file)
420    File.chmod(0060, utf8_file)
421    assert_file.not_world_writable?(utf8_file)
422    File.chmod(0600, utf8_file)
423    assert_file.not_world_writable?(utf8_file)
424
425    assert_file.not_world_writable?(nofile)
426  end if POSIX
427
428  def test_executable_p
429    File.chmod(0100, regular_file)
430    assert_file.executable?(regular_file)
431    File.chmod(0600, regular_file)
432    assert_file.not_executable?(regular_file)
433
434    File.chmod(0100, utf8_file)
435    assert_file.executable?(utf8_file)
436    File.chmod(0600, utf8_file)
437    assert_file.not_executable?(utf8_file)
438
439    assert_file.not_executable?(nofile)
440  end if POSIX
441
442  def test_executable_real_p
443    File.chmod(0100, regular_file)
444    assert_file.executable_real?(regular_file)
445    File.chmod(0600, regular_file)
446    assert_file.not_executable_real?(regular_file)
447
448    File.chmod(0100, utf8_file)
449    assert_file.executable_real?(utf8_file)
450    File.chmod(0600, utf8_file)
451    assert_file.not_executable_real?(utf8_file)
452
453    assert_file.not_executable_real?(nofile)
454  end if POSIX
455
456  def test_file_p
457    assert_file.not_file?(@dir)
458    assert_file.file?(regular_file)
459    assert_file.file?(utf8_file)
460    assert_file.not_file?(nofile)
461  end
462
463  def test_zero_p
464    assert_nothing_raised { File.zero?(@dir) }
465    assert_file.not_zero?(regular_file)
466    assert_file.not_zero?(utf8_file)
467    assert_file.zero?(zerofile)
468    assert_file.not_zero?(nofile)
469  end
470
471  def test_empty_p
472    assert_nothing_raised { File.empty?(@dir) }
473    assert_file.not_empty?(regular_file)
474    assert_file.not_empty?(utf8_file)
475    assert_file.empty?(zerofile)
476    assert_file.not_empty?(nofile)
477  end
478
479  def test_size_p
480    assert_nothing_raised { File.size?(@dir) }
481    assert_equal(3, File.size?(regular_file))
482    assert_equal(3, File.size?(utf8_file))
483    assert_file.not_size?(zerofile)
484    assert_file.not_size?(nofile)
485  end
486
487  def test_owned_p
488    assert_file.owned?(regular_file)
489    assert_file.owned?(utf8_file)
490    assert_file.not_owned?(notownedfile) if notownedfile
491  end if POSIX
492
493  def test_grpowned_p ## xxx
494    assert_file.grpowned?(regular_file)
495    assert_file.grpowned?(utf8_file)
496  end if POSIX
497
498  def io_open(file_name)
499    # avoid File.open since we do not want #to_path
500    io = IO.for_fd(IO.sysopen(file_name))
501    yield io
502  ensure
503    io&.close
504  end
505
506  def test_suid
507    assert_file.not_setuid?(regular_file)
508    assert_file.not_setuid?(utf8_file)
509    if suidfile
510      assert_file.setuid?(suidfile)
511      io_open(suidfile) { |io| assert_file.setuid?(io) }
512    end
513  end
514
515  def test_sgid
516    assert_file.not_setgid?(regular_file)
517    assert_file.not_setgid?(utf8_file)
518    if sgidfile
519      assert_file.setgid?(sgidfile)
520      io_open(sgidfile) { |io| assert_file.setgid?(io) }
521    end
522  end
523
524  def test_sticky
525    assert_file.not_sticky?(regular_file)
526    assert_file.not_sticky?(utf8_file)
527    if stickyfile
528      assert_file.sticky?(stickyfile)
529      io_open(stickyfile) { |io| assert_file.sticky?(io) }
530    end
531  end
532
533  def test_path_identical_p
534    assert_file.identical?(regular_file, regular_file)
535    assert_file.not_identical?(regular_file, zerofile)
536    assert_file.not_identical?(regular_file, nofile)
537    assert_file.not_identical?(nofile, regular_file)
538  end
539
540  def path_identical_p(file)
541    [regular_file, utf8_file].each do |file|
542      assert_file.identical?(file, file)
543      assert_file.not_identical?(file, zerofile)
544      assert_file.not_identical?(file, nofile)
545      assert_file.not_identical?(nofile, file)
546    end
547  end
548
549  def test_io_identical_p
550    [regular_file, utf8_file].each do |file|
551      open(file) {|f|
552        assert_file.identical?(f, f)
553        assert_file.identical?(file, f)
554        assert_file.identical?(f, file)
555      }
556    end
557  end
558
559  def test_closed_io_identical_p
560    [regular_file, utf8_file].each do |file|
561      io = open(file) {|f| f}
562      assert_raise(IOError) {
563        File.identical?(file, io)
564      }
565      File.unlink(file)
566      assert_file.not_exist?(file)
567    end
568  end
569
570  def test_s_size
571    assert_integer(File.size(@dir))
572    assert_equal(3, File.size(regular_file))
573    assert_equal(3, File.size(utf8_file))
574    assert_equal(0, File.size(zerofile))
575    assert_raise(Errno::ENOENT) { File.size(nofile) }
576  end
577
578  def test_ftype
579    assert_equal("directory", File.ftype(@dir))
580    assert_equal("file", File.ftype(regular_file))
581    assert_equal("file", File.ftype(utf8_file))
582    assert_equal("link", File.ftype(symlinkfile)) if symlinkfile
583    assert_equal("file", File.ftype(hardlinkfile)) if hardlinkfile
584    assert_raise(Errno::ENOENT) { File.ftype(nofile) }
585  end
586
587  def test_atime
588    [regular_file, utf8_file].each do |file|
589      t1 = File.atime(file)
590      t2 = File.open(file) {|f| f.atime}
591      assert_kind_of(Time, t1)
592      assert_kind_of(Time, t2)
593      # High Sierra's APFS can handle nano-sec precise.
594      # t1 value is difference from t2 on APFS.
595      if Bug::File::Fs.fsname(Dir.tmpdir) == "apfs"
596        assert_equal(t1.to_i, t2.to_i)
597      else
598        assert_equal(t1, t2)
599      end
600    end
601    assert_raise(Errno::ENOENT) { File.atime(nofile) }
602  end
603
604  def test_mtime
605    [regular_file, utf8_file].each do |file|
606      t1 = File.mtime(file)
607      t2 = File.open(file) {|f| f.mtime}
608      assert_kind_of(Time, t1)
609      assert_kind_of(Time, t2)
610      assert_equal(t1, t2)
611    end
612    assert_raise(Errno::ENOENT) { File.mtime(nofile) }
613  end
614
615  def test_ctime
616    [regular_file, utf8_file].each do |file|
617      t1 = File.ctime(file)
618      t2 = File.open(file) {|f| f.ctime}
619      assert_kind_of(Time, t1)
620      assert_kind_of(Time, t2)
621      assert_equal(t1, t2)
622    end
623    assert_raise(Errno::ENOENT) { File.ctime(nofile) }
624  end
625
626  def test_chmod
627    [regular_file, utf8_file].each do |file|
628      assert_equal(1, File.chmod(0444, file))
629      assert_equal(0444, File.stat(file).mode % 01000)
630      assert_equal(0, File.open(file) {|f| f.chmod(0222)})
631      assert_equal(0222, File.stat(file).mode % 01000)
632      File.chmod(0600, file)
633    end
634    assert_raise(Errno::ENOENT) { File.chmod(0600, nofile) }
635  end if POSIX
636
637  def test_lchmod
638    [regular_file, utf8_file].each do |file|
639      assert_equal(1, File.lchmod(0444, file))
640      assert_equal(0444, File.stat(file).mode % 01000)
641      File.lchmod(0600, regular_file)
642    end
643    assert_raise(Errno::ENOENT) { File.lchmod(0600, nofile) }
644  rescue NotImplementedError
645  end if POSIX
646
647  def test_chown ## xxx
648  end
649
650  def test_lchown ## xxx
651  end
652
653  def test_symlink
654    return unless symlinkfile
655    assert_equal("link", File.ftype(symlinkfile))
656    assert_raise(Errno::EEXIST) { File.symlink(regular_file, regular_file) }
657    assert_raise(Errno::EEXIST) { File.symlink(utf8_file, utf8_file) }
658  end
659
660  def test_utime
661    t = Time.local(2000)
662    File.utime(t + 1, t + 2, zerofile)
663    assert_equal(t + 1, File.atime(zerofile))
664    assert_equal(t + 2, File.mtime(zerofile))
665  end
666
667  def test_utime_symlinkfile
668    return unless symlinkfile
669    t = Time.local(2000)
670    stat = File.lstat(symlinkfile)
671    assert_equal(1, File.utime(t, t, symlinkfile))
672    assert_equal(t, File.stat(regular_file).atime)
673    assert_equal(t, File.stat(regular_file).mtime)
674  end
675
676  def test_lutime
677    return unless File.respond_to?(:lutime)
678    return unless symlinkfile
679
680    r = File.stat(regular_file)
681    t = Time.local(2000)
682    File.lutime(t + 1, t + 2, symlinkfile)
683  rescue NotImplementedError => e
684    skip(e.message)
685  else
686    stat = File.stat(regular_file)
687    assert_equal(r.atime, stat.atime)
688    assert_equal(r.mtime, stat.mtime)
689
690    stat = File.lstat(symlinkfile)
691    assert_equal(t + 1, stat.atime)
692    assert_equal(t + 2, stat.mtime)
693  end
694
695  def test_hardlink
696    return unless hardlinkfile
697    assert_equal("file", File.ftype(hardlinkfile))
698    assert_raise(Errno::EEXIST) { File.link(regular_file, regular_file) }
699    assert_raise(Errno::EEXIST) { File.link(utf8_file, utf8_file) }
700  end
701
702  def test_readlink
703    return unless symlinkfile
704    assert_equal(regular_file, File.readlink(symlinkfile))
705    assert_raise(Errno::EINVAL) { File.readlink(regular_file) }
706    assert_raise(Errno::EINVAL) { File.readlink(utf8_file) }
707    assert_raise(Errno::ENOENT) { File.readlink(nofile) }
708    if fs = Encoding.find("filesystem")
709      assert_equal(fs, File.readlink(symlinkfile).encoding)
710    end
711  rescue NotImplementedError
712  end
713
714  def test_readlink_long_path
715    return unless symlinkfile
716    bug9157 = '[ruby-core:58592] [Bug #9157]'
717    assert_separately(["-", symlinkfile, bug9157], "#{<<~begin}#{<<~"end;"}")
718    begin
719      symlinkfile, bug9157 = *ARGV
720      100.step(1000, 100) do |n|
721        File.unlink(symlinkfile)
722        link = "foo"*n
723        begin
724          File.symlink(link, symlinkfile)
725        rescue Errno::ENAMETOOLONG
726          break
727        end
728        assert_equal(link, File.readlink(symlinkfile), bug9157)
729      end
730    end;
731  end
732
733  if NTFS
734    def test_readlink_junction
735      base = File.basename(nofile)
736      err = IO.popen(%W"cmd.exe /c mklink /j #{base} .", chdir: @dir, err: %i[child out], &:read)
737      skip err unless $?.success?
738      assert_equal(@dir, File.readlink(nofile))
739    end
740
741    def test_realpath_mount_point
742      vol = IO.popen(["mountvol", DRIVE, "/l"], &:read).strip
743      Dir.mkdir(mnt = File.join(@dir, mntpnt = "mntpnt"))
744      system("mountvol", mntpnt, vol, chdir: @dir)
745      assert_equal(mnt, File.realpath(mnt))
746    ensure
747      system("mountvol", mntpnt, "/d", chdir: @dir)
748    end
749  end
750
751  def test_unlink
752    assert_equal(1, File.unlink(regular_file))
753    make_file("foo", regular_file)
754
755    assert_equal(1, File.unlink(utf8_file))
756    make_file("foo", utf8_file)
757
758    assert_raise(Errno::ENOENT) { File.unlink(nofile) }
759  end
760
761  def test_rename
762    [regular_file, utf8_file].each do |file|
763      assert_equal(0, File.rename(file, nofile))
764      assert_file.not_exist?(file)
765      assert_file.exist?(nofile)
766      assert_equal(0, File.rename(nofile, file))
767      assert_raise(Errno::ENOENT) { File.rename(nofile, file) }
768    end
769  end
770
771  def test_umask
772    prev = File.umask(0777)
773    assert_equal(0777, File.umask)
774    open(nofile, "w") { }
775    assert_equal(0, File.stat(nofile).mode % 01000)
776    File.unlink(nofile)
777    assert_equal(0777, File.umask(prev))
778    assert_raise(ArgumentError) { File.umask(0, 1, 2) }
779  end if POSIX
780
781  def test_expand_path
782    assert_equal(regular_file, File.expand_path(File.basename(regular_file), File.dirname(regular_file)))
783    assert_equal(utf8_file, File.expand_path(File.basename(utf8_file), File.dirname(utf8_file)))
784  end
785
786  if NTFS
787    def test_expand_path_ntfs
788      [regular_file, utf8_file].each do |file|
789        assert_equal(file, File.expand_path(file + " "))
790        assert_equal(file, File.expand_path(file + "."))
791        assert_equal(file, File.expand_path(file + "::$DATA"))
792      end
793      assert_match(/\Ac:\//i, File.expand_path('c:'), '[ruby-core:31591]')
794      assert_match(/\Ac:\//i, File.expand_path('c:foo', 'd:/bar'))
795      assert_match(/\Ae:\//i, File.expand_path('e:foo', 'd:/bar'))
796      assert_match(%r'\Ac:/bar/foo\z'i, File.expand_path('c:foo', 'c:/bar'))
797    end
798  end
799
800  case RUBY_PLATFORM
801  when /darwin/
802    def test_expand_path_hfs
803      ["\u{feff}", *"\u{2000}"..."\u{2100}"].each do |c|
804        file = regular_file + c
805        full_path = File.expand_path(file)
806        mesg = proc {File.basename(full_path).dump}
807        begin
808          open(file) {}
809        rescue
810          # High Sierra's APFS cannot use filenames with undefined character
811          next if Bug::File::Fs.fsname(Dir.tmpdir) == "apfs"
812          assert_equal(file, full_path, mesg)
813        else
814          assert_equal(regular_file, full_path, mesg)
815        end
816      end
817    end
818  end
819
820  if DRIVE
821    def test_expand_path_absolute
822      assert_match(%r"\Az:/foo\z"i, File.expand_path('/foo', "z:/bar"))
823      assert_match(%r"\A//host/share/foo\z"i, File.expand_path('/foo', "//host/share/bar"))
824      assert_match(%r"\A#{DRIVE}/foo\z"i, File.expand_path('/foo'))
825    end
826  else
827    def test_expand_path_absolute
828      assert_equal("/foo", File.expand_path('/foo'))
829    end
830  end
831
832  def test_expand_path_memsize
833    bug9934 = '[ruby-core:63114] [Bug #9934]'
834    require "objspace"
835    path = File.expand_path("/foo")
836    assert_operator(ObjectSpace.memsize_of(path), :<=, path.bytesize + GC::INTERNAL_CONSTANTS[:RVALUE_SIZE], bug9934)
837    path = File.expand_path("/a"*25)
838    assert_equal(path.bytesize+1 + GC::INTERNAL_CONSTANTS[:RVALUE_SIZE], ObjectSpace.memsize_of(path), bug9934)
839  end
840
841  def test_expand_path_encoding
842    drive = (DRIVE ? 'C:' : '')
843    if Encoding.find("filesystem") == Encoding::CP1251
844      a = "#{drive}/\u3042\u3044\u3046\u3048\u304a".encode("cp932")
845    else
846      a = "#{drive}/\u043f\u0440\u0438\u0432\u0435\u0442".encode("cp1251")
847    end
848    assert_equal(a, File.expand_path(a))
849    a = "#{drive}/\225\\\\"
850    if File::ALT_SEPARATOR == '\\'
851      [%W"cp437 #{drive}/\225", %W"cp932 #{drive}/\225\\"]
852    elsif File.directory?("#{@dir}/\\")
853      [%W"cp437 /\225", %W"cp932 /\225\\"]
854    else
855      [["cp437", a], ["cp932", a]]
856    end.each do |cp, expected|
857      assert_equal(expected.force_encoding(cp), File.expand_path(a.dup.force_encoding(cp)), cp)
858    end
859
860    path = "\u3042\u3044\u3046\u3048\u304a".encode("EUC-JP")
861    assert_equal("#{Dir.pwd}/#{path}".encode("CP932"), File.expand_path(path).encode("CP932"))
862
863    path = "\u3042\u3044\u3046\u3048\u304a".encode("CP51932")
864    assert_equal("#{Dir.pwd}/#{path}", File.expand_path(path))
865
866    assert_incompatible_encoding {|d| File.expand_path(d)}
867  end
868
869  def test_expand_path_encoding_filesystem
870    home = ENV["HOME"]
871    ENV["HOME"] = "#{DRIVE}/UserHome"
872
873    path = "~".encode("US-ASCII")
874    dir = "C:/".encode("IBM437")
875    fs = Encoding.find("filesystem")
876
877    assert_equal fs, File.expand_path(path).encoding
878    assert_equal fs, File.expand_path(path, dir).encoding
879  ensure
880    ENV["HOME"] = home
881  end
882
883  UnknownUserHome = "~foo_bar_baz_unknown_user_wahaha".freeze
884
885  def test_expand_path_home
886    assert_kind_of(String, File.expand_path("~")) if ENV["HOME"]
887    assert_raise(ArgumentError) { File.expand_path(UnknownUserHome) }
888    assert_raise(ArgumentError) { File.expand_path(UnknownUserHome, "/") }
889    begin
890      bug3630 = '[ruby-core:31537]'
891      home = ENV["HOME"]
892      home_drive = ENV["HOMEDRIVE"]
893      home_path = ENV["HOMEPATH"]
894      user_profile = ENV["USERPROFILE"]
895      ENV["HOME"] = nil
896      ENV["HOMEDRIVE"] = nil
897      ENV["HOMEPATH"] = nil
898      ENV["USERPROFILE"] = nil
899      ENV["HOME"] = "~"
900      assert_raise(ArgumentError, bug3630) { File.expand_path("~") }
901      ENV["HOME"] = "."
902      assert_raise(ArgumentError, bug3630) { File.expand_path("~") }
903    ensure
904      ENV["HOME"] = home
905      ENV["HOMEDRIVE"] = home_drive
906      ENV["HOMEPATH"] = home_path
907      ENV["USERPROFILE"] = user_profile
908    end
909  end
910
911  def test_expand_path_home_dir_string
912    home = ENV["HOME"]
913    new_home = "#{DRIVE}/UserHome"
914    ENV["HOME"] = new_home
915    bug8034 = "[ruby-core:53168]"
916
917    assert_equal File.join(new_home, "foo"), File.expand_path("foo", "~"), bug8034
918    assert_equal File.join(new_home, "bar", "foo"), File.expand_path("foo", "~/bar"), bug8034
919
920    assert_raise(ArgumentError) { File.expand_path(".", UnknownUserHome) }
921    assert_nothing_raised(ArgumentError) { File.expand_path("#{DRIVE}/", UnknownUserHome) }
922    ENV["HOME"] = "#{DRIVE}UserHome"
923    assert_raise(ArgumentError) { File.expand_path("~") }
924  ensure
925    ENV["HOME"] = home
926  end
927
928  if /mswin|mingw/ =~ RUBY_PLATFORM
929    def test_expand_path_home_memory_leak_in_path
930      assert_no_memory_leak_at_expand_path_home('', 'in path')
931    end
932
933    def test_expand_path_home_memory_leak_in_base
934      assert_no_memory_leak_at_expand_path_home('".",', 'in base')
935    end
936
937    def assert_no_memory_leak_at_expand_path_home(arg, message)
938      prep = 'ENV["HOME"] = "foo"*100'
939      assert_no_memory_leak([], prep, <<-TRY, "memory leaked at non-absolute home #{message}")
940      10000.times do
941        begin
942          File.expand_path(#{arg}"~/a")
943        rescue ArgumentError => e
944          next
945        ensure
946          abort("ArgumentError (non-absolute home) expected") unless e
947        end
948      end
949      GC.start
950      TRY
951    end
952  end
953
954
955  def test_expand_path_remove_trailing_alternative_data
956    assert_equal File.join(rootdir, "aaa"), File.expand_path("#{rootdir}/aaa::$DATA")
957    assert_equal File.join(rootdir, "aa:a"), File.expand_path("#{rootdir}/aa:a:$DATA")
958    assert_equal File.join(rootdir, "aaa:$DATA"), File.expand_path("#{rootdir}/aaa:$DATA")
959  end if DRIVE
960
961  def test_expand_path_resolve_empty_string_current_directory
962    assert_equal(Dir.pwd, File.expand_path(""))
963  end
964
965  def test_expand_path_resolve_dot_current_directory
966    assert_equal(Dir.pwd, File.expand_path("."))
967  end
968
969  def test_expand_path_resolve_file_name_relative_current_directory
970    assert_equal(File.join(Dir.pwd, "foo"), File.expand_path("foo"))
971  end
972
973  def test_ignore_nil_dir_string
974    assert_equal(File.join(Dir.pwd, "foo"), File.expand_path("foo", nil))
975  end
976
977  def test_expand_path_resolve_file_name_and_dir_string_relative
978    assert_equal(File.join(Dir.pwd, "bar", "foo"),
979      File.expand_path("foo", "bar"))
980  end
981
982  def test_expand_path_cleanup_dots_file_name
983    bug = "[ruby-talk:18512]"
984
985    assert_equal(File.join(Dir.pwd, ".a"), File.expand_path(".a"), bug)
986    assert_equal(File.join(Dir.pwd, "..a"), File.expand_path("..a"), bug)
987
988    if DRIVE
989      # cleanup dots only on Windows
990      assert_equal(File.join(Dir.pwd, "a"), File.expand_path("a."), bug)
991      assert_equal(File.join(Dir.pwd, "a"), File.expand_path("a.."), bug)
992    else
993      assert_equal(File.join(Dir.pwd, "a."), File.expand_path("a."), bug)
994      assert_equal(File.join(Dir.pwd, "a.."), File.expand_path("a.."), bug)
995    end
996  end
997
998  def test_expand_path_converts_a_pathname_to_an_absolute_pathname_using_a_complete_path
999    assert_equal(@dir, File.expand_path("", "#{@dir}"))
1000    assert_equal(File.join(@dir, "a"), File.expand_path("a", "#{@dir}"))
1001    assert_equal(File.join(@dir, "a"), File.expand_path("../a", "#{@dir}/xxx"))
1002    assert_equal(rootdir, File.expand_path(".", "#{rootdir}"))
1003  end
1004
1005  def test_expand_path_ignores_supplied_dir_if_path_contains_a_drive_letter
1006    assert_equal(rootdir, File.expand_path(rootdir, "D:/"))
1007  end if DRIVE
1008
1009  def test_expand_path_removes_trailing_slashes_from_absolute_path
1010    assert_equal(File.join(rootdir, "foo"), File.expand_path("#{rootdir}foo/"))
1011    assert_equal(File.join(rootdir, "foo.rb"), File.expand_path("#{rootdir}foo.rb/"))
1012  end
1013
1014  def test_expand_path_removes_trailing_spaces_from_absolute_path
1015    assert_equal(File.join(rootdir, "a"), File.expand_path("#{rootdir}a "))
1016  end if DRIVE
1017
1018  def test_expand_path_converts_a_pathname_which_starts_with_a_slash_using_dir_s_drive
1019    assert_match(%r"\Az:/foo\z"i, File.expand_path('/foo', "z:/bar"))
1020  end if DRIVE
1021
1022  def test_expand_path_converts_a_pathname_which_starts_with_a_slash_and_unc_pathname
1023    assert_equal("//foo", File.expand_path('//foo', "//bar"))
1024    assert_equal("//bar/foo", File.expand_path('/foo', "//bar"))
1025    assert_equal("//foo", File.expand_path('//foo', "/bar"))
1026  end if DRIVE
1027
1028  def test_expand_path_converts_a_dot_with_unc_dir
1029    assert_equal("//", File.expand_path('.', "//"))
1030  end
1031
1032  def test_expand_path_preserves_unc_path_root
1033    assert_equal("//", File.expand_path("//"))
1034    assert_equal("//", File.expand_path("//."))
1035    assert_equal("//", File.expand_path("//.."))
1036  end
1037
1038  def test_expand_path_converts_a_pathname_which_starts_with_a_slash_using_host_share
1039    assert_match(%r"\A//host/share/foo\z"i, File.expand_path('/foo', "//host/share/bar"))
1040  end if DRIVE
1041
1042  def test_expand_path_converts_a_pathname_which_starts_with_a_slash_using_a_current_drive
1043    assert_match(%r"\A#{DRIVE}/foo\z"i, File.expand_path('/foo'))
1044  end
1045
1046  def test_expand_path_returns_tainted_strings_or_not
1047    assert_equal(true, File.expand_path('foo').tainted?)
1048    assert_equal(true, File.expand_path('foo'.taint).tainted?)
1049    assert_equal(true, File.expand_path('/foo'.taint).tainted?)
1050    assert_equal(true, File.expand_path('foo', 'bar').tainted?)
1051    assert_equal(true, File.expand_path('foo', '/bar'.taint).tainted?)
1052    assert_equal(true, File.expand_path('foo'.taint, '/bar').tainted?)
1053    assert_equal(true, File.expand_path('~').tainted?) if ENV["HOME"]
1054
1055    if DRIVE
1056      assert_equal(true, File.expand_path('/foo').tainted?)
1057      assert_equal(false, File.expand_path('//foo').tainted?)
1058      assert_equal(true, File.expand_path('C:/foo'.taint).tainted?)
1059      assert_equal(false, File.expand_path('C:/foo').tainted?)
1060      assert_equal(true, File.expand_path('foo', '/bar').tainted?)
1061      assert_equal(true, File.expand_path('foo', 'C:/bar'.taint).tainted?)
1062      assert_equal(true, File.expand_path('foo'.taint, 'C:/bar').tainted?)
1063      assert_equal(false, File.expand_path('foo', 'C:/bar').tainted?)
1064      assert_equal(false, File.expand_path('C:/foo/../bar').tainted?)
1065      assert_equal(false, File.expand_path('foo', '//bar').tainted?)
1066    else
1067      assert_equal(false, File.expand_path('/foo').tainted?)
1068      assert_equal(false, File.expand_path('foo', '/bar').tainted?)
1069    end
1070  end
1071
1072  def test_expand_path_converts_a_pathname_to_an_absolute_pathname_using_home_as_base
1073    old_home = ENV["HOME"]
1074    home = ENV["HOME"] = "#{DRIVE}/UserHome"
1075    assert_equal(home, File.expand_path("~"))
1076    assert_equal(home, File.expand_path("~", "C:/FooBar"))
1077    assert_equal(File.join(home, "a"), File.expand_path("~/a", "C:/FooBar"))
1078  ensure
1079    ENV["HOME"] = old_home
1080  end
1081
1082  def test_expand_path_converts_a_pathname_to_an_absolute_pathname_using_unc_home
1083    old_home = ENV["HOME"]
1084    unc_home = ENV["HOME"] = "//UserHome"
1085    assert_equal(unc_home, File.expand_path("~"))
1086  ensure
1087    ENV["HOME"] = old_home
1088  end if DRIVE
1089
1090  def test_expand_path_does_not_modify_a_home_string_argument
1091    old_home = ENV["HOME"]
1092    home = ENV["HOME"] = "#{DRIVE}/UserHome"
1093    str = "~/a"
1094    assert_equal("#{home}/a", File.expand_path(str))
1095    assert_equal("~/a", str)
1096  ensure
1097    ENV["HOME"] = old_home
1098  end
1099
1100  def test_expand_path_raises_argument_error_for_any_supplied_username
1101    bug = '[ruby-core:39597]'
1102    assert_raise(ArgumentError, bug) { File.expand_path("~anything") }
1103  end if DRIVE
1104
1105  def test_expand_path_for_existent_username
1106    user = ENV['USER']
1107    skip "ENV['USER'] is not set" unless user
1108    assert_equal(ENV['HOME'], File.expand_path("~#{user}"))
1109  end unless DRIVE
1110
1111  def test_expand_path_error_for_nonexistent_username
1112    user = "\u{3086 3046 3066 3044}:\u{307F 3084 304A 3046}"
1113    assert_raise_with_message(ArgumentError, /#{user}/) {File.expand_path("~#{user}")}
1114  end unless DRIVE
1115
1116  def test_expand_path_error_for_non_absolute_home
1117    old_home = ENV["HOME"]
1118    ENV["HOME"] = "./UserHome"
1119    assert_raise_with_message(ArgumentError, /non-absolute home/) {File.expand_path("~")}
1120  ensure
1121    ENV["HOME"] = old_home
1122  end
1123
1124  def test_expand_path_raises_a_type_error_if_not_passed_a_string_type
1125    assert_raise(TypeError) { File.expand_path(1) }
1126    assert_raise(TypeError) { File.expand_path(nil) }
1127    assert_raise(TypeError) { File.expand_path(true) }
1128  end
1129
1130  def test_expand_path_expands_dot_dir
1131    assert_equal("#{DRIVE}/dir", File.expand_path("#{DRIVE}/./dir"))
1132  end
1133
1134  def test_expand_path_does_not_expand_wildcards
1135    assert_equal("#{DRIVE}/*", File.expand_path("./*", "#{DRIVE}/"))
1136    assert_equal("#{Dir.pwd}/*", File.expand_path("./*", Dir.pwd))
1137    assert_equal("#{DRIVE}/?", File.expand_path("./?", "#{DRIVE}/"))
1138    assert_equal("#{Dir.pwd}/?", File.expand_path("./?", Dir.pwd))
1139  end if DRIVE
1140
1141  def test_expand_path_does_not_modify_the_string_argument
1142    str = "./a/b/../c"
1143    assert_equal("#{Dir.pwd}/a/c", File.expand_path(str, Dir.pwd))
1144    assert_equal("./a/b/../c", str)
1145  end
1146
1147  def test_expand_path_returns_a_string_when_passed_a_string_subclass
1148    sub = Class.new(String)
1149    str = sub.new "./a/b/../c"
1150    path = File.expand_path(str, Dir.pwd)
1151    assert_equal("#{Dir.pwd}/a/c", path)
1152    assert_instance_of(String, path)
1153  end
1154
1155  def test_expand_path_accepts_objects_that_have_a_to_path_method
1156    klass = Class.new { def to_path; "a/b/c"; end }
1157    obj = klass.new
1158    assert_equal("#{Dir.pwd}/a/b/c", File.expand_path(obj))
1159  end
1160
1161  def test_expand_path_with_drive_letter
1162    bug10858 = '[ruby-core:68130] [Bug #10858]'
1163    assert_match(%r'/bar/foo\z'i, File.expand_path('z:foo', 'bar'), bug10858)
1164    assert_equal('z:/bar/foo', File.expand_path('z:foo', '/bar'), bug10858)
1165  end if DRIVE
1166
1167  if /darwin/ =~ RUBY_PLATFORM and Encoding.find("filesystem") == Encoding::UTF_8
1168    def test_expand_path_compose
1169      pp = Object.new.extend(Test::Unit::Assertions)
1170      def pp.mu_pp(str) #:nodoc:
1171        str.dump
1172      end
1173
1174      Dir.mktmpdir do |dir|
1175        Dir.chdir(dir) do
1176          orig = %W"d\u{e9}tente x\u{304c 304e 3050 3052 3054}"
1177          orig.each do |o|
1178            Dir.mkdir(o)
1179            n = Dir.chdir(o) {File.expand_path(".")}
1180            pp.assert_equal(o, File.basename(n))
1181          end
1182        end
1183      end
1184    end
1185  end
1186
1187  def test_basename
1188    assert_equal(File.basename(regular_file).sub(/\.test$/, ""), File.basename(regular_file, ".test"))
1189    assert_equal(File.basename(utf8_file).sub(/\.test$/, ""), File.basename(utf8_file, ".test"))
1190    assert_equal("", s = File.basename(""))
1191    assert_not_predicate(s, :frozen?, '[ruby-core:24199]')
1192    assert_equal("foo", s = File.basename("foo"))
1193    assert_not_predicate(s, :frozen?, '[ruby-core:24199]')
1194    assert_equal("foo", File.basename("foo", ".ext"))
1195    assert_equal("foo", File.basename("foo.ext", ".ext"))
1196    assert_equal("foo", File.basename("foo.ext", ".*"))
1197  end
1198
1199  if NTFS
1200    def test_basename_strip
1201      [regular_file, utf8_file].each do |file|
1202        basename = File.basename(file)
1203        assert_equal(basename, File.basename(file + " "))
1204        assert_equal(basename, File.basename(file + "."))
1205        assert_equal(basename, File.basename(file + "::$DATA"))
1206        basename.chomp!(".test")
1207        assert_equal(basename, File.basename(file + " ", ".test"))
1208        assert_equal(basename, File.basename(file + ".", ".test"))
1209        assert_equal(basename, File.basename(file + "::$DATA", ".test"))
1210        assert_equal(basename, File.basename(file + " ", ".*"))
1211        assert_equal(basename, File.basename(file + ".", ".*"))
1212        assert_equal(basename, File.basename(file + "::$DATA", ".*"))
1213      end
1214    end
1215  else
1216    def test_basename_strip
1217      [regular_file, utf8_file].each do |file|
1218        basename = File.basename(file)
1219        assert_equal(basename + " ", File.basename(file + " "))
1220        assert_equal(basename + ".", File.basename(file + "."))
1221        assert_equal(basename + "::$DATA", File.basename(file + "::$DATA"))
1222        assert_equal(basename + " ", File.basename(file + " ", ".test"))
1223        assert_equal(basename + ".", File.basename(file + ".", ".test"))
1224        assert_equal(basename + "::$DATA", File.basename(file + "::$DATA", ".test"))
1225        assert_equal(basename, File.basename(file + ".", ".*"))
1226        basename.chomp!(".test")
1227        assert_equal(basename, File.basename(file + " ", ".*"))
1228        assert_equal(basename, File.basename(file + "::$DATA", ".*"))
1229      end
1230    end
1231  end
1232
1233  if File::ALT_SEPARATOR == '\\'
1234    def test_basename_backslash
1235      a = "foo/\225\\\\"
1236      [%W"cp437 \225", %W"cp932 \225\\"].each do |cp, expected|
1237        assert_equal(expected.force_encoding(cp), File.basename(a.dup.force_encoding(cp)), cp)
1238      end
1239    end
1240  end
1241
1242  def test_basename_encoding
1243    assert_incompatible_encoding {|d| File.basename(d)}
1244    assert_incompatible_encoding {|d| File.basename(d, ".*")}
1245    assert_raise(Encoding::CompatibilityError) {File.basename("foo.ext", ".*".encode("utf-16le"))}
1246
1247    s = "foo\x93_a".force_encoding("cp932")
1248    assert_equal(s, File.basename(s, "_a"))
1249
1250    s = "\u4032.\u3024"
1251    assert_equal(s, File.basename(s, ".\x95\\".force_encoding("cp932")))
1252  end
1253
1254  def test_dirname
1255    assert_equal(@dir, File.dirname(regular_file))
1256    assert_equal(@dir, File.dirname(utf8_file))
1257    assert_equal(".", File.dirname(""))
1258  end
1259
1260  def test_dirname_encoding
1261    assert_incompatible_encoding {|d| File.dirname(d)}
1262  end
1263
1264  if File::ALT_SEPARATOR == '\\'
1265    def test_dirname_backslash
1266      a = "\225\\\\foo"
1267      [%W"cp437 \225", %W"cp932 \225\\"].each do |cp, expected|
1268        assert_equal(expected.force_encoding(cp), File.dirname(a.dup.force_encoding(cp)), cp)
1269      end
1270    end
1271  end
1272
1273  def test_extname
1274    assert_equal(".test", File.extname(regular_file))
1275    assert_equal(".test", File.extname(utf8_file))
1276    prefixes = ["", "/", ".", "/.", "bar/.", "/bar/."]
1277    infixes = ["", " ", "."]
1278    infixes2 = infixes + [".ext "]
1279    appendixes = [""]
1280    if NTFS
1281      appendixes << " " << "." << "::$DATA" << "::$DATA.bar"
1282    end
1283    prefixes.each do |prefix|
1284      appendixes.each do |appendix|
1285        infixes.each do |infix|
1286          path = "#{prefix}foo#{infix}#{appendix}"
1287          assert_equal("", File.extname(path), "File.extname(#{path.inspect})")
1288        end
1289        infixes2.each do |infix|
1290          path = "#{prefix}foo#{infix}.ext#{appendix}"
1291          assert_equal(".ext", File.extname(path), "File.extname(#{path.inspect})")
1292        end
1293      end
1294    end
1295    bug3175 = '[ruby-core:29627]'
1296    assert_equal(".rb", File.extname("/tmp//bla.rb"), bug3175)
1297
1298    assert_incompatible_encoding {|d| File.extname(d)}
1299  end
1300
1301  def test_split
1302    [regular_file, utf8_file].each do |file|
1303      d, b = File.split(file)
1304      assert_equal(File.dirname(file), d)
1305      assert_equal(File.basename(file), b)
1306    end
1307  end
1308
1309  def test_join
1310    s = "foo" + File::SEPARATOR + "bar" + File::SEPARATOR + "baz"
1311    assert_equal(s, File.join("foo", "bar", "baz"))
1312    assert_equal(s, File.join(["foo", "bar", "baz"]))
1313
1314    o = Object.new
1315    def o.to_path; "foo"; end
1316    assert_equal(s, File.join(o, "bar", "baz"))
1317    assert_equal(s, File.join("foo" + File::SEPARATOR, "bar", File::SEPARATOR + "baz"))
1318  end
1319
1320  def test_join_alt_separator
1321    if File::ALT_SEPARATOR == '\\'
1322      a = "\225\\"
1323      b = "foo"
1324      [%W"cp437 \225\\foo", %W"cp932 \225\\/foo"].each do |cp, expected|
1325        assert_equal(expected.force_encoding(cp), File.join(a.dup.force_encoding(cp), b.dup.force_encoding(cp)), cp)
1326      end
1327    end
1328  end
1329
1330  def test_join_ascii_incompatible
1331    bug7168 = '[ruby-core:48012]'
1332    names = %w"a b".map {|s| s.encode(Encoding::UTF_16LE)}
1333    assert_raise(Encoding::CompatibilityError, bug7168) {File.join(*names)}
1334    assert_raise(Encoding::CompatibilityError, bug7168) {File.join(names)}
1335
1336    a = Object.new
1337    b = names[1]
1338    names = [a, "b"]
1339    a.singleton_class.class_eval do
1340      define_method(:to_path) do
1341        names[1] = b
1342        "a"
1343      end
1344    end
1345    assert_raise(Encoding::CompatibilityError, bug7168) {File.join(names)}
1346  end
1347
1348  def test_join_with_changed_separator
1349    assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}")
1350    bug = '[ruby-core:79579] [Bug #13223]'
1351    begin;
1352      class File
1353        remove_const :Separator
1354        remove_const :SEPARATOR
1355      end
1356      GC.start
1357      assert_equal("hello/world", File.join("hello", "world"), bug)
1358    end;
1359  end
1360
1361  def test_truncate
1362    [regular_file, utf8_file].each do |file|
1363      assert_equal(0, File.truncate(file, 1))
1364      assert_file.exist?(file)
1365      assert_equal(1, File.size(file))
1366      assert_equal(0, File.truncate(file, 0))
1367      assert_file.exist?(file)
1368      assert_file.zero?(file)
1369      make_file("foo", file)
1370      assert_raise(Errno::ENOENT) { File.truncate(nofile, 0) }
1371
1372      f = File.new(file, "w")
1373      assert_equal(0, f.truncate(2))
1374      assert_file.exist?(file)
1375      assert_equal(2, File.size(file))
1376      assert_equal(0, f.truncate(0))
1377      assert_file.exist?(file)
1378      assert_file.zero?(file)
1379      f.close
1380      make_file("foo", file)
1381
1382      assert_raise(IOError) { File.open(file) {|ff| ff.truncate(0)} }
1383    end
1384  rescue NotImplementedError
1385  end
1386
1387  def test_flock_exclusive
1388    File.open(regular_file, "r+") do |f|
1389      f.flock(File::LOCK_EX)
1390      assert_separately(["-rtimeout", "-", regular_file], "#{<<~begin}#{<<~"end;"}")
1391      begin
1392        open(ARGV[0], "r") do |f|
1393          Timeout.timeout(0.1) do
1394            assert(!f.flock(File::LOCK_SH|File::LOCK_NB))
1395          end
1396        end
1397      end;
1398      assert_separately(["-rtimeout", "-", regular_file], "#{<<~begin}#{<<~"end;"}")
1399      begin
1400        open(ARGV[0], "r") do |f|
1401          assert_raise(Timeout::Error) do
1402            Timeout.timeout(0.1) do
1403              f.flock(File::LOCK_SH)
1404            end
1405          end
1406        end
1407      end;
1408      f.flock(File::LOCK_UN)
1409    end
1410  rescue NotImplementedError
1411  end
1412
1413  def test_flock_shared
1414    File.open(regular_file, "r+") do |f|
1415      f.flock(File::LOCK_SH)
1416      assert_separately(["-rtimeout", "-", regular_file], "#{<<~begin}#{<<~"end;"}")
1417      begin
1418        open(ARGV[0], "r") do |f|
1419          Timeout.timeout(0.1) do
1420            assert(f.flock(File::LOCK_SH))
1421          end
1422        end
1423      end;
1424      assert_separately(["-rtimeout", "-", regular_file], "#{<<~begin}#{<<~"end;"}")
1425      begin
1426        open(ARGV[0], "r+") do |f|
1427          assert_raise(Timeout::Error) do
1428            Timeout.timeout(0.1) do
1429              f.flock(File::LOCK_EX)
1430            end
1431          end
1432        end
1433      end;
1434      f.flock(File::LOCK_UN)
1435    end
1436  rescue NotImplementedError
1437  end
1438
1439  def test_test
1440    fn1 = regular_file
1441    hardlinkfile
1442    sleep(1.1)
1443    fn2 = fn1 + "2"
1444    make_file("foo", fn2)
1445    [
1446      @dir,
1447      fn1,
1448      zerofile,
1449      notownedfile,
1450      suidfile,
1451      sgidfile,
1452      stickyfile,
1453      symlinkfile,
1454      hardlinkfile,
1455      chardev,
1456      blockdev,
1457      fifo,
1458      socket
1459    ].compact.each do |f|
1460      assert_equal(File.atime(f), test(?A, f))
1461      assert_equal(File.ctime(f), test(?C, f))
1462      assert_equal(File.mtime(f), test(?M, f))
1463      assert_equal(File.blockdev?(f), test(?b, f))
1464      assert_equal(File.chardev?(f), test(?c, f))
1465      assert_equal(File.directory?(f), test(?d, f))
1466      assert_equal(File.exist?(f), test(?e, f))
1467      assert_equal(File.file?(f), test(?f, f))
1468      assert_equal(File.setgid?(f), test(?g, f))
1469      assert_equal(File.grpowned?(f), test(?G, f))
1470      assert_equal(File.sticky?(f), test(?k, f))
1471      assert_equal(File.symlink?(f), test(?l, f))
1472      assert_equal(File.owned?(f), test(?o, f))
1473      assert_nothing_raised { test(?O, f) }
1474      assert_equal(File.pipe?(f), test(?p, f))
1475      assert_equal(File.readable?(f), test(?r, f))
1476      assert_equal(File.readable_real?(f), test(?R, f))
1477      assert_equal(File.size?(f), test(?s, f))
1478      assert_equal(File.socket?(f), test(?S, f))
1479      assert_equal(File.setuid?(f), test(?u, f))
1480      assert_equal(File.writable?(f), test(?w, f))
1481      assert_equal(File.writable_real?(f), test(?W, f))
1482      assert_equal(File.executable?(f), test(?x, f))
1483      assert_equal(File.executable_real?(f), test(?X, f))
1484      assert_equal(File.zero?(f), test(?z, f))
1485    end
1486    assert_equal(false, test(?-, @dir, fn1))
1487    assert_equal(true, test(?-, fn1, fn1))
1488    assert_equal(true, test(?=, fn1, fn1))
1489    assert_equal(false, test(?>, fn1, fn1))
1490    assert_equal(false, test(?<, fn1, fn1))
1491    unless /cygwin/ =~ RUBY_PLATFORM
1492      assert_equal(false, test(?=, fn1, fn2))
1493      assert_equal(false, test(?>, fn1, fn2))
1494      assert_equal(true, test(?>, fn2, fn1))
1495      assert_equal(true, test(?<, fn1, fn2))
1496      assert_equal(false, test(?<, fn2, fn1))
1497    end
1498    assert_raise(ArgumentError) { test }
1499    assert_raise(Errno::ENOENT) { test(?A, nofile) }
1500    assert_raise(ArgumentError) { test(?a) }
1501    assert_raise(ArgumentError) { test("\0".ord) }
1502  end
1503
1504  def test_stat_init
1505    fn1 = regular_file
1506    hardlinkfile
1507    sleep(1.1)
1508    fn2 = fn1 + "2"
1509    make_file("foo", fn2)
1510    fs1, fs2 = File::Stat.new(fn1), File::Stat.new(fn2)
1511    assert_nothing_raised do
1512      assert_equal(0, fs1 <=> fs1)
1513      assert_equal(-1, fs1 <=> fs2)
1514      assert_equal(1, fs2 <=> fs1)
1515      assert_nil(fs1 <=> nil)
1516      assert_integer(fs1.dev)
1517      assert_integer_or_nil(fs1.rdev)
1518      assert_integer_or_nil(fs1.dev_major)
1519      assert_integer_or_nil(fs1.dev_minor)
1520      assert_integer_or_nil(fs1.rdev_major)
1521      assert_integer_or_nil(fs1.rdev_minor)
1522      assert_integer(fs1.ino)
1523      assert_integer(fs1.mode)
1524      assert_equal(hardlinkfile ? 2 : 1, fs1.nlink)
1525      assert_integer(fs1.uid)
1526      assert_integer(fs1.gid)
1527      assert_equal(3, fs1.size)
1528      assert_integer_or_nil(fs1.blksize)
1529      assert_integer_or_nil(fs1.blocks)
1530      assert_kind_of(Time, fs1.atime)
1531      assert_kind_of(Time, fs1.mtime)
1532      assert_kind_of(Time, fs1.ctime)
1533      assert_kind_of(String, fs1.inspect)
1534    end
1535    assert_raise(Errno::ENOENT) { File::Stat.new(nofile) }
1536    assert_kind_of(File::Stat, File::Stat.new(fn1).dup)
1537    assert_raise(TypeError) do
1538      File::Stat.new(fn1).instance_eval { initialize_copy(0) }
1539    end
1540  end
1541
1542  def test_stat_new_utf8
1543    assert_nothing_raised do
1544      File::Stat.new(utf8_file)
1545    end
1546  end
1547
1548  def test_stat_ftype
1549    assert_equal("directory", File::Stat.new(@dir).ftype)
1550    assert_equal("file", File::Stat.new(regular_file).ftype)
1551    # File::Stat uses stat
1552    assert_equal("file", File::Stat.new(symlinkfile).ftype) if symlinkfile
1553    assert_equal("file", File::Stat.new(hardlinkfile).ftype) if hardlinkfile
1554  end
1555
1556  def test_stat_directory_p
1557    assert_predicate(File::Stat.new(@dir), :directory?)
1558    assert_not_predicate(File::Stat.new(regular_file), :directory?)
1559  end
1560
1561  def test_stat_pipe_p
1562    assert_not_predicate(File::Stat.new(@dir), :pipe?)
1563    assert_not_predicate(File::Stat.new(regular_file), :pipe?)
1564    assert_predicate(File::Stat.new(fifo), :pipe?) if fifo
1565    IO.pipe {|r, w|
1566      assert_predicate(r.stat, :pipe?)
1567      assert_predicate(w.stat, :pipe?)
1568    }
1569  end
1570
1571  def test_stat_symlink_p
1572    assert_not_predicate(File::Stat.new(@dir), :symlink?)
1573    assert_not_predicate(File::Stat.new(regular_file), :symlink?)
1574    # File::Stat uses stat
1575    assert_not_predicate(File::Stat.new(symlinkfile), :symlink?) if symlinkfile
1576    assert_not_predicate(File::Stat.new(hardlinkfile), :symlink?) if hardlinkfile
1577  end
1578
1579  def test_stat_socket_p
1580    assert_not_predicate(File::Stat.new(@dir), :socket?)
1581    assert_not_predicate(File::Stat.new(regular_file), :socket?)
1582    assert_predicate(File::Stat.new(socket), :socket?) if socket
1583  end
1584
1585  def test_stat_blockdev_p
1586    assert_not_predicate(File::Stat.new(@dir), :blockdev?)
1587    assert_not_predicate(File::Stat.new(regular_file), :blockdev?)
1588    assert_predicate(File::Stat.new(blockdev), :blockdev?) if blockdev
1589  end
1590
1591  def test_stat_chardev_p
1592    assert_not_predicate(File::Stat.new(@dir), :chardev?)
1593    assert_not_predicate(File::Stat.new(regular_file), :chardev?)
1594    assert_predicate(File::Stat.new(chardev), :chardev?) if chardev
1595  end
1596
1597  def test_stat_readable_p
1598    return if Process.euid == 0
1599    File.chmod(0200, regular_file)
1600    assert_not_predicate(File::Stat.new(regular_file), :readable?)
1601    File.chmod(0600, regular_file)
1602    assert_predicate(File::Stat.new(regular_file), :readable?)
1603  end if POSIX
1604
1605  def test_stat_readable_real_p
1606    return if Process.euid == 0
1607    File.chmod(0200, regular_file)
1608    assert_not_predicate(File::Stat.new(regular_file), :readable_real?)
1609    File.chmod(0600, regular_file)
1610    assert_predicate(File::Stat.new(regular_file), :readable_real?)
1611  end if POSIX
1612
1613  def test_stat_world_readable_p
1614    File.chmod(0006, regular_file)
1615    assert_predicate(File::Stat.new(regular_file), :world_readable?)
1616    File.chmod(0060, regular_file)
1617    assert_not_predicate(File::Stat.new(regular_file), :world_readable?)
1618    File.chmod(0600, regular_file)
1619    assert_not_predicate(File::Stat.new(regular_file), :world_readable?)
1620  end if POSIX
1621
1622  def test_stat_writable_p
1623    return if Process.euid == 0
1624    File.chmod(0400, regular_file)
1625    assert_not_predicate(File::Stat.new(regular_file), :writable?)
1626    File.chmod(0600, regular_file)
1627    assert_predicate(File::Stat.new(regular_file), :writable?)
1628  end if POSIX
1629
1630  def test_stat_writable_real_p
1631    return if Process.euid == 0
1632    File.chmod(0400, regular_file)
1633    assert_not_predicate(File::Stat.new(regular_file), :writable_real?)
1634    File.chmod(0600, regular_file)
1635    assert_predicate(File::Stat.new(regular_file), :writable_real?)
1636  end if POSIX
1637
1638  def test_stat_world_writable_p
1639    File.chmod(0006, regular_file)
1640    assert_predicate(File::Stat.new(regular_file), :world_writable?)
1641    File.chmod(0060, regular_file)
1642    assert_not_predicate(File::Stat.new(regular_file), :world_writable?)
1643    File.chmod(0600, regular_file)
1644    assert_not_predicate(File::Stat.new(regular_file), :world_writable?)
1645  end if POSIX
1646
1647  def test_stat_executable_p
1648    File.chmod(0100, regular_file)
1649    assert_predicate(File::Stat.new(regular_file), :executable?)
1650    File.chmod(0600, regular_file)
1651    assert_not_predicate(File::Stat.new(regular_file), :executable?)
1652  end if POSIX
1653
1654  def test_stat_executable_real_p
1655    File.chmod(0100, regular_file)
1656    assert_predicate(File::Stat.new(regular_file), :executable_real?)
1657    File.chmod(0600, regular_file)
1658    assert_not_predicate(File::Stat.new(regular_file), :executable_real?)
1659  end if POSIX
1660
1661  def test_stat_file_p
1662    assert_not_predicate(File::Stat.new(@dir), :file?)
1663    assert_predicate(File::Stat.new(regular_file), :file?)
1664  end
1665
1666  def test_stat_zero_p
1667    assert_nothing_raised { File::Stat.new(@dir).zero? }
1668    assert_not_predicate(File::Stat.new(regular_file), :zero?)
1669    assert_predicate(File::Stat.new(zerofile), :zero?)
1670  end
1671
1672  def test_stat_size_p
1673    assert_nothing_raised { File::Stat.new(@dir).size? }
1674    assert_equal(3, File::Stat.new(regular_file).size?)
1675    assert_not_predicate(File::Stat.new(zerofile), :size?)
1676  end
1677
1678  def test_stat_owned_p
1679    assert_predicate(File::Stat.new(regular_file), :owned?)
1680    assert_not_predicate(File::Stat.new(notownedfile), :owned?) if notownedfile
1681  end if POSIX
1682
1683  def test_stat_grpowned_p ## xxx
1684    assert_predicate(File::Stat.new(regular_file), :grpowned?)
1685  end if POSIX
1686
1687  def test_stat_suid
1688    assert_not_predicate(File::Stat.new(regular_file), :setuid?)
1689    assert_predicate(File::Stat.new(suidfile), :setuid?) if suidfile
1690  end
1691
1692  def test_stat_sgid
1693    assert_not_predicate(File::Stat.new(regular_file), :setgid?)
1694    assert_predicate(File::Stat.new(sgidfile), :setgid?) if sgidfile
1695  end
1696
1697  def test_stat_sticky
1698    assert_not_predicate(File::Stat.new(regular_file), :sticky?)
1699    assert_predicate(File::Stat.new(stickyfile), :sticky?) if stickyfile
1700  end
1701
1702  def test_stat_size
1703    assert_integer(File::Stat.new(@dir).size)
1704    assert_equal(3, File::Stat.new(regular_file).size)
1705    assert_equal(0, File::Stat.new(zerofile).size)
1706  end
1707
1708  def test_stat_special_file
1709    # test for special files such as pagefile.sys on Windows
1710    assert_nothing_raised do
1711      Dir::glob("C:/*.sys") {|f| File::Stat.new(f) }
1712    end
1713  end if DRIVE
1714
1715  def test_path_check
1716    assert_nothing_raised { ENV["PATH"] }
1717  end
1718
1719  def test_size
1720    [regular_file, utf8_file].each do |file|
1721      assert_equal(3, File.open(file) {|f| f.size })
1722      File.open(file, "a") do |f|
1723        f.write("bar")
1724        assert_equal(6, f.size)
1725      end
1726    end
1727  end
1728
1729  def test_absolute_path
1730    assert_equal(File.join(Dir.pwd, "~foo"), File.absolute_path("~foo"))
1731    dir = File.expand_path("/bar")
1732    assert_equal(File.join(dir, "~foo"), File.absolute_path("~foo", dir))
1733  end
1734end
1735