# frozen_string_literal: false
require 'test/unit'

require 'tmpdir'
require 'fileutils'

class TestDir < Test::Unit::TestCase

  def setup
    @verbose = $VERBOSE
    @root = File.realpath(Dir.mktmpdir('__test_dir__'))
    @nodir = File.join(@root, "dummy")
    @dirs = []
    for i in "a".."z"
      if i.ord % 2 == 0
        FileUtils.touch(File.join(@root, i))
      else
        FileUtils.mkdir(File.join(@root, i))
        @dirs << File.join(i, "")
      end
    end
    @envs = nil
  end

  def teardown
    $VERBOSE = @verbose
    FileUtils.remove_entry_secure @root if File.directory?(@root)
    ENV.update(@envs) if @envs
  end

  def setup_envs(envs = %w"HOME LOGDIR")
    @envs ||= {}
    envs.each do |e, v|
      @envs[e] = ENV.delete(e)
      ENV[e] = v if v
    end
  end

  def test_seek
    dir = Dir.open(@root)
    begin
      cache = []
      loop do
        pos = dir.tell
        break unless name = dir.read
        cache << [pos, name]
      end
      for x,y in cache.sort_by {|z| z[0] % 3 } # shuffle
        dir.seek(x)
        assert_equal(y, dir.read)
      end
    ensure
      dir.close
    end
  end

  def test_nodir
    assert_raise(Errno::ENOENT) { Dir.open(@nodir) }
  end

  def test_inspect
    d = Dir.open(@root)
    assert_match(/^#<Dir:#{ Regexp.quote(@root) }>$/, d.inspect)
    assert_match(/^#<Dir:.*>$/, Dir.allocate.inspect)
  ensure
    d.close
  end

  def test_path
    d = Dir.open(@root)
    assert_equal(@root, d.path)
    assert_nil(Dir.allocate.path)
  ensure
    d.close
  end

  def test_set_pos
    d = Dir.open(@root)
    loop do
      i = d.pos
      break unless x = d.read
      d.pos = i
      assert_equal(x, d.read)
    end
  ensure
    d.close
  end

  def test_rewind
    d = Dir.open(@root)
    a = (0..5).map { d.read }
    d.rewind
    b = (0..5).map { d.read }
    assert_equal(a, b)
  ensure
    d.close
  end

  def test_class_chdir
    pwd = Dir.pwd
    setup_envs

    assert_raise(Errno::ENOENT) { Dir.chdir(@nodir) }
    assert_raise(ArgumentError) { Dir.chdir }
    ENV["HOME"] = pwd
    Dir.chdir do
      assert_warning(/conflicting chdir during another chdir block/) { Dir.chdir(pwd) }

      assert_warning(/conflicting chdir during another chdir block/) { Dir.chdir(@root) }
      assert_equal(@root, Dir.pwd)

      assert_warning(/conflicting chdir during another chdir block/) { Dir.chdir(pwd) }

      assert_raise(RuntimeError) { Thread.new { Thread.current.report_on_exception = false; Dir.chdir(@root) }.join }
      assert_raise(RuntimeError) { Thread.new { Thread.current.report_on_exception = false; Dir.chdir(@root) { } }.join }

      assert_warning(/conflicting chdir during another chdir block/) { Dir.chdir(pwd) }

      assert_warning(/conflicting chdir during another chdir block/) { Dir.chdir(@root) }
      assert_equal(@root, Dir.pwd)

      assert_warning(/conflicting chdir during another chdir block/) { Dir.chdir(pwd) }
      Dir.chdir(@root) do
        assert_equal(@root, Dir.pwd)
      end
      assert_equal(pwd, Dir.pwd)
    end

  ensure
    begin
      Dir.chdir(pwd)
    rescue
      abort("cannot return the original directory: #{ pwd }")
    end
  end

  def test_instance_chdir
    pwd = Dir.pwd
    dir = Dir.new(pwd)
    root_dir = Dir.new(@root)
    setup_envs

    ENV["HOME"] = pwd
    ret = root_dir.chdir do |*a|
      assert_empty(a)

      assert_warning(/conflicting chdir during another chdir block/) { dir.chdir }
      assert_warning(/conflicting chdir during another chdir block/) { root_dir.chdir }

      assert_equal(@root, Dir.pwd)

      assert_raise(RuntimeError) { Thread.new { Thread.current.report_on_exception = false; dir.chdir }.join }
      assert_raise(RuntimeError) { Thread.new { Thread.current.report_on_exception = false; dir.chdir{} }.join }

      assert_warning(/conflicting chdir during another chdir block/) { dir.chdir }
      assert_equal(pwd, Dir.pwd)

      assert_warning(/conflicting chdir during another chdir block/) { root_dir.chdir }
      assert_equal(@root, Dir.pwd)

      assert_warning(/conflicting chdir during another chdir block/) { dir.chdir }

      root_dir.chdir do
        assert_equal(@root, Dir.pwd)
      end
      assert_equal(pwd, Dir.pwd)

      42
    end

    assert_equal(42, ret)
  ensure
    begin
      assert_equal(0, dir.chdir)
    rescue
      abort("cannot return the original directory: #{ pwd }")
    end
    dir.close
    root_dir.close
  end

  def test_chdir_conflict
    pwd = Dir.pwd
    q = Thread::Queue.new
    t = Thread.new do
      q.pop
      Dir.chdir(pwd) rescue $!
    end
    Dir.chdir(pwd) do
      q.push nil
      assert_instance_of(RuntimeError, t.value)
    end

    t = Thread.new do
      q.pop
      Dir.chdir(pwd){} rescue $!
    end
    Dir.chdir(pwd) do
      q.push nil
      assert_instance_of(RuntimeError, t.value)
    end
  end

  def test_chroot_nodir
    omit if RUBY_PLATFORM =~ /android/
    assert_raise(NotImplementedError, Errno::ENOENT, Errno::EPERM
		) { Dir.chroot(File.join(@nodir, "")) }
  end

  def test_close
    d = Dir.open(@root)
    d.close
    assert_nothing_raised(IOError) { d.close }
    assert_raise(IOError) { d.read }
  end

  def test_glob
    assert_equal((%w(.) + ("a".."z").to_a).map{|f| File.join(@root, f) },
                 Dir.glob(File.join(@root, "*"), File::FNM_DOTMATCH))
    assert_equal([@root] + ("a".."z").map {|f| File.join(@root, f) },
                 Dir.glob([@root, File.join(@root, "*")]))
    assert_equal([@root] + ("a".."z").map {|f| File.join(@root, f) },
                 Dir.glob([@root, File.join(@root, "*")], sort: false).sort)
    assert_equal([@root] + ("a".."z").map {|f| File.join(@root, f) },
                 Dir.glob([@root, File.join(@root, "*")], sort: true))
    assert_raise_with_message(ArgumentError, /nul-separated/) do
      Dir.glob(@root + "\0\0\0" + File.join(@root, "*"))
    end
    assert_raise_with_message(ArgumentError, /expected true or false/) do
      Dir.glob(@root, sort: 1)
    end
    assert_raise_with_message(ArgumentError, /expected true or false/) do
      Dir.glob(@root, sort: nil)
    end

    assert_equal(("a".."z").step(2).map {|f| File.join(File.join(@root, f), "") },
                 Dir.glob(File.join(@root, "*/")))
    assert_equal([File.join(@root, '//a')], Dir.glob(@root + '//a'))

    FileUtils.touch(File.join(@root, "{}"))
    assert_equal(%w({} a).map{|f| File.join(@root, f) },
                 Dir.glob(File.join(@root, '{\{\},a}')))
    assert_equal([], Dir.glob(File.join(@root, '[')))
    assert_equal([], Dir.glob(File.join(@root, '[a-\\')))

    assert_equal([File.join(@root, "a")], Dir.glob(File.join(@root, 'a\\')))
    assert_equal(("a".."f").map {|f| File.join(@root, f) }, Dir.glob(File.join(@root, '[abc/def]')))

    open(File.join(@root, "}}{}"), "wb") {}
    open(File.join(@root, "}}a"), "wb") {}
    assert_equal(%w(}}{} }}a).map {|f| File.join(@root, f)}, Dir.glob(File.join(@root, '}}{\{\},a}')))
    assert_equal(%w(}}{} }}a b c).map {|f| File.join(@root, f)}, Dir.glob(File.join(@root, '{\}\}{\{\},a},b,c}')))
    assert_raise(ArgumentError) {
      Dir.glob([[@root, File.join(@root, "*")].join("\0")])
    }
  end

  def test_glob_recursive
    bug6977 = '[ruby-core:47418]'
    bug8006 = '[ruby-core:53108] [Bug #8006]'
    Dir.chdir(@root) do
      assert_include(Dir.glob("a/**/*", File::FNM_DOTMATCH), "a/.", bug8006)

      Dir.mkdir("a/b")
      assert_not_include(Dir.glob("a/**/*", File::FNM_DOTMATCH), "a/b/.")

      FileUtils.mkdir_p("a/b/c/d/e/f")
      assert_equal(["a/b/c/d/e/f"], Dir.glob("a/**/e/f"), bug6977)
      assert_equal(["a/b/c/d/e/f"], Dir.glob("a/**/d/e/f"), bug6977)
      assert_equal(["a/b/c/d/e/f"], Dir.glob("a/**/c/d/e/f"), bug6977)
      assert_equal(["a/b/c/d/e/f"], Dir.glob("a/**/b/c/d/e/f"), bug6977)
      assert_equal(["a/b/c/d/e/f"], Dir.glob("a/**/c/?/e/f"), bug6977)
      assert_equal(["a/b/c/d/e/f"], Dir.glob("a/**/c/**/d/e/f"), bug6977)
      assert_equal(["a/b/c/d/e/f"], Dir.glob("a/**/c/**/d/e/f"), bug6977)

      bug8283 = '[ruby-core:54387] [Bug #8283]'
      dirs = ["a/.x", "a/b/.y"]
      FileUtils.mkdir_p(dirs)
      dirs.map {|dir| open("#{dir}/z", "w") {}}
      assert_equal([], Dir.glob("a/**/z"), bug8283)
      assert_equal(["a/.x/z"], Dir.glob("a/**/.x/z"), bug8283)
      assert_equal(["a/.x/z"], Dir.glob("a/.x/**/z"), bug8283)
      assert_equal(["a/b/.y/z"], Dir.glob("a/**/.y/z"), bug8283)
    end
  end

  def test_glob_recursive_directory
    Dir.chdir(@root) do
      ['d', 'e'].each do |path|
        FileUtils.mkdir_p("c/#{path}/a/b/c")
        FileUtils.touch("c/#{path}/a/a.file")
        FileUtils.touch("c/#{path}/a/b/b.file")
        FileUtils.touch("c/#{path}/a/b/c/c.file")
      end
      bug15540 = '[ruby-core:91110] [Bug #15540]'
      assert_equal(["c/d/a/", "c/d/a/b/", "c/d/a/b/c/", "c/e/a/", "c/e/a/b/", "c/e/a/b/c/"],
                   Dir.glob('c/{d,e}/a/**/'), bug15540)

      assert_equal(["c/e/a/", "c/e/a/b/", "c/e/a/b/c/", "c/d/a/", "c/d/a/b/", "c/d/a/b/c/"],
                   Dir.glob('c/{e,d}/a/**/'))
    end
  end

  def test_glob_starts_with_brace
    Dir.chdir(@root) do
      bug15649 = '[ruby-core:91728] [Bug #15649]'
      assert_equal(["#{@root}/a", "#{@root}/b"],
                   Dir.glob("{#{@root}/a,#{@root}/b}"), bug15649)
    end
  end

  def test_glob_recursive_with_brace
    Dir.chdir(@root) do
      bug19042 = '[ruby-core:110220] [Bug #19042]'
      %w"c/dir_a c/dir_b c/dir_b/dir".each do |d|
        Dir.mkdir(d)
      end
      expected = %w"c/dir_a/file c/dir_b/dir/file"
      expected.each do |f|
        File.write(f, "")
      end
      assert_equal(expected, Dir.glob("**/{dir_a,dir_b/dir}/file"), bug19042)
    end
  end

  def test_glob_order
    Dir.chdir(@root) do
      assert_equal(["#{@root}/a", "#{@root}/b"], Dir.glob("#{@root}/[ba]"))
      assert_equal(["#{@root}/b", "#{@root}/a"], Dir.glob(%W"#{@root}/b #{@root}/a"))
      assert_equal(["#{@root}/b", "#{@root}/a"], Dir.glob("#{@root}/{b,a}"))
    end
    assert_equal(["a", "b"], Dir.glob("[ba]", base: @root))
    assert_equal(["b", "a"], Dir.glob(%W"b a", base: @root))
    assert_equal(["b", "a"], Dir.glob("{b,a}", base: @root))
  end

  if Process.const_defined?(:RLIMIT_NOFILE)
    def test_glob_too_may_open_files
      assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}", chdir: @root)
      begin;
        n = 16
        Process.setrlimit(Process::RLIMIT_NOFILE, n)
        files = []
        begin
          n.times {files << File.open('b')}
        rescue Errno::EMFILE, Errno::ENFILE => e
        end
        assert_raise(e.class) {
          Dir.glob('*')
        }
      end;
    end
  end

  def test_glob_base
    files = %w[a/foo.c c/bar.c]
    files.each {|n| File.write(File.join(@root, n), "")}
    Dir.mkdir(File.join(@root, "a/dir"))
    dirs = @dirs + %w[a/dir/]
    dirs.sort!

    assert_equal(files, Dir.glob("*/*.c", base: @root))
    assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: ".")})
    assert_equal(%w[foo.c], Dir.chdir(@root) {Dir.glob("*.c", base: "a")})
    assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: "")})
    assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: nil)})
    assert_equal(@dirs, Dir.glob("*/", base: @root))
    assert_equal(@dirs, Dir.chdir(@root) {Dir.glob("*/", base: ".")})
    assert_equal(%w[dir/], Dir.chdir(@root) {Dir.glob("*/", base: "a")})
    assert_equal(@dirs, Dir.chdir(@root) {Dir.glob("*/", base: "")})
    assert_equal(@dirs, Dir.chdir(@root) {Dir.glob("*/", base: nil)})
    assert_equal(dirs, Dir.glob("**/*/", base: @root))
    assert_equal(dirs, Dir.chdir(@root) {Dir.glob("**/*/", base: ".")})
    assert_equal(%w[dir/], Dir.chdir(@root) {Dir.glob("**/*/", base: "a")})
    assert_equal(dirs, Dir.chdir(@root) {Dir.glob("**/*/", base: "")})
    assert_equal(dirs, Dir.chdir(@root) {Dir.glob("**/*/", base: nil)})

    assert_equal(files, Dir.glob("*/*.c", base: @root, sort: false).sort)
    assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: ".", sort: false).sort})
    assert_equal(%w[foo.c], Dir.chdir(@root) {Dir.glob("*.c", base: "a", sort: false).sort})
    assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: "", sort: false).sort})
    assert_equal(files, Dir.chdir(@root) {Dir.glob("*/*.c", base: nil, sort: false).sort})
    assert_equal(@dirs, Dir.glob("*/", base: @root))
    assert_equal(@dirs, Dir.chdir(@root) {Dir.glob("*/", base: ".", sort: false).sort})
    assert_equal(%w[dir/], Dir.chdir(@root) {Dir.glob("*/", base: "a", sort: false).sort})
    assert_equal(@dirs, Dir.chdir(@root) {Dir.glob("*/", base: "", sort: false).sort})
    assert_equal(@dirs, Dir.chdir(@root) {Dir.glob("*/", base: nil, sort: false).sort})
    assert_equal(dirs, Dir.glob("**/*/", base: @root))
    assert_equal(dirs, Dir.chdir(@root) {Dir.glob("**/*/", base: ".", sort: false).sort})
    assert_equal(%w[dir/], Dir.chdir(@root) {Dir.glob("**/*/", base: "a", sort: false).sort})
    assert_equal(dirs, Dir.chdir(@root) {Dir.glob("**/*/", base: "", sort: false).sort})
    assert_equal(dirs, Dir.chdir(@root) {Dir.glob("**/*/", base: nil, sort: false).sort})
  end

  def test_glob_base_dir
    files = %w[a/foo.c c/bar.c]
    files.each {|n| File.write(File.join(@root, n), "")}
    Dir.mkdir(File.join(@root, "a/dir"))
    dirs = @dirs + %w[a/dir/]
    dirs.sort!

    assert_equal(files, Dir.open(@root) {|d| Dir.glob("*/*.c", base: d)})
    assert_equal(%w[foo.c], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("*.c", base: d)}})
    assert_equal(@dirs, Dir.open(@root) {|d| Dir.glob("*/", base: d)})
    assert_equal(%w[dir/], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("*/", base: d)}})
    assert_equal(dirs, Dir.open(@root) {|d| Dir.glob("**/*/", base: d)})
    assert_equal(%w[dir/], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("**/*/", base: d)}})

    assert_equal(files, Dir.open(@root) {|d| Dir.glob("*/*.c", base: d, sort: false).sort})
    assert_equal(%w[foo.c], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("*.c", base: d, sort: false).sort}})
    assert_equal(@dirs, Dir.open(@root) {|d| Dir.glob("*/", base: d, sort: false).sort})
    assert_equal(%w[dir/], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("*/", base: d, sort: false).sort}})
    assert_equal(dirs, Dir.open(@root) {|d| Dir.glob("**/*/", base: d, sort: false).sort})
    assert_equal(%w[dir/], Dir.chdir(@root) {Dir.open("a") {|d| Dir.glob("**/*/", base: d, sort: false).sort}})
  end

  def test_glob_ignore_casefold_invalid_encoding
    bug14456 = "[ruby-core:85448]"
    filename = "\u00AAa123".encode('ISO-8859-1')
    File.write(File.join(@root, filename), "")
    matches = Dir.chdir(@root) {|d| Dir.glob("*a123".encode('UTF-8'), File::FNM_CASEFOLD)}
    assert_equal(1, matches.size, bug14456)
    matches.each{|f| f.force_encoding('ISO-8859-1')}
    # Handle MacOS/Windows, which saves under a different filename
    assert_include([filename, "\u00C2\u00AAa123".encode('ISO-8859-1')], matches.first, bug14456)
  end

  def assert_entries(entries, children_only = false)
    entries.sort!
    expected = ("a".."z").to_a
    expected = %w(. ..) + expected unless children_only
    assert_equal(expected, entries)
  end

  def test_entries
    assert_entries(Dir.open(@root) {|dir| dir.entries})
    assert_entries(Dir.entries(@root))
    assert_raise(ArgumentError) {Dir.entries(@root+"\0")}
    [Encoding::UTF_8, Encoding::ASCII_8BIT].each do |enc|
      assert_equal(enc, Dir.entries(@root, encoding: enc).first.encoding)
    end
  end

  def test_foreach
    assert_entries(Dir.open(@root) {|dir| dir.each.to_a})
    assert_entries(Dir.foreach(@root).to_a)
    assert_raise(ArgumentError) {Dir.foreach(@root+"\0").to_a}
    newdir = @root+"/new"
    e = Dir.foreach(newdir)
    assert_raise(Errno::ENOENT) {e.to_a}
    Dir.mkdir(newdir)
    File.write(newdir+"/a", "")
    assert_equal(%w[. .. a], e.to_a.sort)
    [Encoding::UTF_8, Encoding::ASCII_8BIT].each do |enc|
      e = Dir.foreach(newdir, encoding: enc)
      assert_equal(enc, e.to_a.first.encoding)
    end
  end

  def test_children
    assert_entries(Dir.open(@root) {|dir| dir.children}, true)
    assert_entries(Dir.children(@root), true)
    assert_raise(ArgumentError) {Dir.children(@root+"\0")}
    [Encoding::UTF_8, Encoding::ASCII_8BIT].each do |enc|
      assert_equal(enc, Dir.children(@root, encoding: enc).first.encoding)
    end
  end

  def test_each_child
    assert_entries(Dir.open(@root) {|dir| dir.each_child.to_a}, true)
    assert_entries(Dir.each_child(@root).to_a, true)
    assert_raise(ArgumentError) {Dir.each_child(@root+"\0").to_a}
    newdir = @root+"/new"
    e = Dir.each_child(newdir)
    assert_raise(Errno::ENOENT) {e.to_a}
    Dir.mkdir(newdir)
    File.write(newdir+"/a", "")
    assert_equal(%w[a], e.to_a)
    [Encoding::UTF_8, Encoding::ASCII_8BIT].each do |enc|
      e = Dir.each_child(newdir, encoding: enc)
      assert_equal(enc, e.to_a.first.encoding)
    end
  end

  def test_dir_enc
    dir = Dir.open(@root, encoding: "UTF-8")
    begin
      while name = dir.read
	assert_equal(Encoding.find("UTF-8"), name.encoding)
      end
    ensure
      dir.close
    end

    dir = Dir.open(@root, encoding: "ASCII-8BIT")
    begin
      while name = dir.read
	assert_equal(Encoding.find("ASCII-8BIT"), name.encoding)
      end
    ensure
      dir.close
    end
  end

  def test_unknown_keywords
    bug8060 = '[ruby-dev:47152] [Bug #8060]'
    assert_raise_with_message(ArgumentError, /unknown keyword/, bug8060) do
      Dir.open(@root, xawqij: "a") {}
    end
  end

  def test_symlink
    begin
      ["dummy", *"a".."z"].each do |f|
	File.symlink(File.join(@root, f),
		     File.join(@root, "symlink-#{ f }"))
      end
    rescue NotImplementedError, Errno::EACCES
      return
    end

    assert_equal([*"a".."z", *"symlink-a".."symlink-z"].each_slice(2).map {|f, _| File.join(@root, f + "/") }.sort,
		 Dir.glob(File.join(@root, "*/")))

    assert_equal([@root + "/", *[*"a".."z"].each_slice(2).map {|f, _| File.join(@root, f + "/") }],
                 Dir.glob(File.join(@root, "**/")))
  end

  def test_glob_metachar
    bug8597 = '[ruby-core:55764] [Bug #8597]'
    assert_empty(Dir.glob(File.join(@root, "<")), bug8597)
  end

  def test_glob_cases
    feature5994 = "[ruby-core:42469] [Feature #5994]"
    feature5994 << "\nDir.glob should return the filename with actual cases on the filesystem"
    Dir.chdir(File.join(@root, "a")) do
      open("FileWithCases", "w") {}
      return unless File.exist?("filewithcases")
      assert_equal(%w"FileWithCases", Dir.glob("filewithcases"), feature5994)
    end
    Dir.chdir(@root) do
      assert_equal(%w"a/FileWithCases", Dir.glob("A/filewithcases"), feature5994)
    end
  end

  def test_glob_super_root
    bug9648 = '[ruby-core:61552] [Bug #9648]'
    roots = Dir.glob("/*")
    assert_equal(roots.map {|n| "/..#{n}"}, Dir.glob("/../*"), bug9648)
  end

  if /mswin|mingw/ =~ RUBY_PLATFORM
    def test_glob_legacy_short_name
      bug10819 = '[ruby-core:67954] [Bug #10819]'
      bug11206 = '[ruby-core:69435] [Bug #11206]'
      omit unless /\A\w:/ =~ ENV["ProgramFiles"]
      short = "#$&/PROGRA~1"
      omit unless File.directory?(short)
      entries = Dir.glob("#{short}/Common*")
      assert_not_empty(entries, bug10819)
      long = File.expand_path(short)
      assert_equal(Dir.glob("#{long}/Common*"), entries, bug10819)
      wild = short.sub(/1\z/, '*')
      assert_not_include(Dir.glob(wild), long, bug11206)
      assert_include(Dir.glob(wild, File::FNM_SHORTNAME), long, bug10819)
      assert_empty(entries - Dir.glob("#{wild}/Common*", File::FNM_SHORTNAME), bug10819)
    end

    def test_home_windows
      setup_envs(%w[HOME USERPROFILE HOMEDRIVE HOMEPATH])

      ENV['HOME'] = "C:\\ruby\\home"
      assert_equal("C:/ruby/home", Dir.home)

      ENV['USERPROFILE'] = "C:\\ruby\\userprofile"
      assert_equal("C:/ruby/home", Dir.home)
      ENV.delete('HOME')
      assert_equal("C:/ruby/userprofile", Dir.home)

      ENV['HOMEDRIVE'] = "C:"
      ENV['HOMEPATH'] = "\\ruby\\homepath"
      assert_equal("C:/ruby/userprofile", Dir.home)
      ENV.delete('USERPROFILE')
      assert_equal("C:/ruby/homepath", Dir.home)
    end

    def test_home_at_startup_windows
      env = {'HOME' => "C:\\ruby\\home"}
      args = [env]
      assert_separately(args, "#{<<~"begin;"}\n#{<<~'end;'}")
      begin;
        assert_equal("C:/ruby/home", Dir.home)
      end;

      env['USERPROFILE'] = "C:\\ruby\\userprofile"
      assert_separately(args, "#{<<~"begin;"}\n#{<<~'end;'}")
      begin;
        assert_equal("C:/ruby/home", Dir.home)
      end;

      env['HOME'] = nil
      assert_separately(args, "#{<<~"begin;"}\n#{<<~'end;'}")
      begin;
        assert_equal("C:/ruby/userprofile", Dir.home)
      end;

      env['HOMEDRIVE'] = "C:"
      env['HOMEPATH'] = "\\ruby\\homepath"
      assert_separately(args, "#{<<~"begin;"}\n#{<<~'end;'}")
      begin;
        assert_equal("C:/ruby/userprofile", Dir.home)
      end;

      env['USERPROFILE'] = nil
      assert_separately(args, "#{<<~"begin;"}\n#{<<~'end;'}")
      begin;
        assert_equal("C:/ruby/homepath", Dir.home)
      end;
    end
  end

  def test_home
    setup_envs

    ENV["HOME"] = @nodir
    assert_nothing_raised(ArgumentError) do
      assert_equal(@nodir, Dir.home)
    end
    assert_nothing_raised(ArgumentError) do
      assert_equal(@nodir, Dir.home(""))
    end
    if user = ENV["USER"]
      tilde = windows? ? "~" : "~#{user}"
      assert_nothing_raised(ArgumentError) do
        assert_equal(File.expand_path(tilde), Dir.home(user))
      end
    end
    %W[no:such:user \u{7559 5b88}:\u{756a}].each do |user|
      assert_raise_with_message(ArgumentError, /#{user}/) {Dir.home(user)}
    end
  end

  if Encoding.find("filesystem") == Encoding::UTF_8
    # On Windows and macOS, file system encoding is always UTF-8.
    def test_home_utf8
      setup_envs

      ENV["HOME"] = "/\u{e4}~\u{1f3e0}"
      assert_equal("/\u{e4}~\u{1f3e0}", Dir.home)
    end
  end

  def test_symlinks_not_resolved
    Dir.mktmpdir do |dirname|
      Dir.chdir(dirname) do
        begin
          File.symlink('some-dir', 'dir-symlink')
        rescue NotImplementedError, Errno::EACCES
          return
        end

        Dir.mkdir('some-dir')
        File.write('some-dir/foo', 'some content')

        assert_equal [ 'dir-symlink', 'some-dir' ], Dir['*']
        assert_equal [ 'dir-symlink', 'some-dir', 'some-dir/foo' ], Dir['**/*']
      end
    end
  end

  def test_fileno
    Dir.open(".") {|d|
      if d.respond_to? :fileno
        assert_kind_of(Integer, d.fileno)
      else
        assert_raise(NotImplementedError) { d.fileno }
      end
    }
  end

  def test_for_fd
    if Dir.respond_to? :for_fd
      begin
        new_dir = Dir.new('..')
        for_fd_dir = Dir.for_fd(new_dir.fileno)
        assert_equal(new_dir.chdir{Dir.pwd}, for_fd_dir.chdir{Dir.pwd})
      ensure
        new_dir&.close
        for_fd_dir&.close
      end
    else
      assert_raise(NotImplementedError) { Dir.for_fd(0) }
    end
  end

  def test_empty?
    assert_not_send([Dir, :empty?, @root])
    a = File.join(@root, "a")
    assert_send([Dir, :empty?, a])
    %w[A .dot].each do |tmp|
      tmp = File.join(a, tmp)
      open(tmp, "w") {}
      assert_not_send([Dir, :empty?, a])
      File.delete(tmp)
      assert_send([Dir, :empty?, a])
      Dir.mkdir(tmp)
      assert_not_send([Dir, :empty?, a])
      Dir.rmdir(tmp)
      assert_send([Dir, :empty?, a])
    end
    assert_raise(Errno::ENOENT) {Dir.empty?(@nodir)}
    assert_not_send([Dir, :empty?, File.join(@root, "b")])
    assert_raise(ArgumentError) {Dir.empty?(@root+"\0")}
  end

  def test_glob_gc_for_fd
    assert_separately(["-C", @root], "#{<<-"begin;"}\n#{<<-"end;"}", timeout: 3)
    begin;
      Process.setrlimit(Process::RLIMIT_NOFILE, 50)
      begin
        fs = []
        tap {tap {tap {(0..100).each {fs << open(IO::NULL)}}}}
      rescue Errno::EMFILE
      ensure
        fs.clear
      end
      list = Dir.glob("*")
      assert_not_empty(list)
      assert_equal([*"a".."z"], list)
    end;
  end if defined?(Process::RLIMIT_NOFILE)

  def test_glob_array_with_destructive_element
    args = Array.new(100, "")
    pat = Struct.new(:ary).new(args)
    args.push(pat, *Array.new(100) {"."*40})
    def pat.to_path
      ary.clear
      GC.start
      ""
    end
    assert_empty(Dir.glob(args))
  end
end
