# frozen_string_literal: false
require 'test/unit'
require 'timeout'
require 'tmpdir'
require 'tempfile'
require 'fileutils'

class TestArgf < Test::Unit::TestCase
  def setup
    @tmpdir = Dir.mktmpdir
    @tmp_count = 0
    @t1 = make_tempfile("argf-foo", %w"1 2", binmode: true)
    @t2 = make_tempfile("argf-bar", %w"3 4", binmode: true)
    @t3 = make_tempfile("argf-baz", %w"5 6", binmode: true)
  end

  def teardown
    FileUtils.rmtree(@tmpdir)
  end

  def make_tempfile(basename = "argf-qux", data = %w[foo bar baz], binmode: false)
    @tmp_count += 1
    path = "#{@tmpdir}/#{basename}-#{@tmp_count}"
    File.open(path, "w") do |f|
      f.binmode if binmode
      f.puts(*data)
      f
    end
  end

  def ruby(*args, external_encoding: Encoding::UTF_8)
    args = ['-e', '$>.write($<.read)'] if args.empty?
    ruby = EnvUtil.rubybin
    f = IO.popen([ruby] + args, 'r+', external_encoding: external_encoding)
    yield(f)
  ensure
    f.close unless !f || f.closed?
  end

  def no_safe_rename
    /cygwin|mswin|mingw|bccwin/ =~ RUBY_PLATFORM
  end

  def assert_src_expected(src, args = nil, line: caller_locations(1, 1)[0].lineno+1)
    args ||= [@t1.path, @t2.path, @t3.path]
    expected = src.split(/^/)
    ruby('-e', src, *args) do |f|
      expected.each_with_index do |e, i|
        /#=> *(.*)/ =~ e or next
        a = f.gets
        assert_not_nil(a, "[ruby-dev:34445]: remained")
        assert_equal($1, a.chomp, "[ruby-dev:34445]: line #{line+i}")
      end
    end
  end

  def test_argf
    assert_src_expected("#{<<~"{#"}\n#{<<~'};'}")
    {#
      a = ARGF
      b = a.dup
      p [a.gets.chomp, a.lineno, b.gets.chomp, b.lineno] #=> ["1", 1, "1", 1]
      p [a.gets.chomp, a.lineno, b.gets.chomp, b.lineno] #=> ["2", 2, "2", 2]
      a.rewind
      b.rewind
      p [a.gets.chomp, a.lineno, b.gets.chomp, b.lineno] #=> ["1", 1, "1", 1]
      p [a.gets.chomp, a.lineno, b.gets.chomp, b.lineno] #=> ["2", 2, "2", 2]
      p [a.gets.chomp, a.lineno, b.gets.chomp, b.lineno] #=> ["3", 3, "3", 3]
      p [a.gets.chomp, a.lineno, b.gets.chomp, b.lineno] #=> ["4", 4, "4", 4]
      p [a.gets.chomp, a.lineno, b.gets.chomp, b.lineno] #=> ["5", 5, "5", 5]
      a.rewind
      b.rewind
      p [a.gets.chomp, a.lineno, b.gets.chomp, b.lineno] #=> ["5", 5, "5", 5]
      p [a.gets.chomp, a.lineno, b.gets.chomp, b.lineno] #=> ["6", 6, "6", 6]
    };
  end

  def test_lineno
    assert_src_expected("#{<<~"{#"}\n#{<<~'};'}")
    {#
      a = ARGF
      a.gets; p($.) #=> 1
      a.gets; p($.) #=> 2
      a.gets; p($.) #=> 3
      a.rewind; p($.) #=> 3
      a.gets; p($.) #=> 3
      a.gets; p($.) #=> 4
      a.rewind; p($.) #=> 4
      a.gets; p($.) #=> 3
      a.lineno = 1000; p($.) #=> 1000
      a.gets; p($.) #=> 1001
      a.gets; p($.) #=> 1002
      $. = 2000
      a.gets; p($.) #=> 2001
      a.gets; p($.) #=> 2001
    };
  end

  def test_lineno2
    assert_src_expected("#{<<~"{#"}\n#{<<~'};'}")
    {#
      a = ARGF.dup
      a.gets; p($.) #=> 1
      a.gets; p($.) #=> 2
      a.gets; p($.) #=> 1
      a.rewind; p($.) #=> 1
      a.gets; p($.) #=> 1
      a.gets; p($.) #=> 2
      a.gets; p($.) #=> 1
      a.lineno = 1000; p($.) #=> 1
      a.gets; p($.) #=> 2
      a.gets; p($.) #=> 2
      $. = 2000
      a.gets; p($.) #=> 2000
      a.gets; p($.) #=> 2000
    };
  end

  def test_lineno3
    expected = %w"1 1 1 2 2 2 3 3 1 4 4 2"
    assert_in_out_err(["-", @t1.path, @t2.path],
                      "#{<<~"{#"}\n#{<<~'};'}", expected, [], "[ruby-core:25205]")
    {#
      ARGF.each do |line|
        puts [$., ARGF.lineno, ARGF.file.lineno]
      end
    };
  end

  def test_lineno_after_shebang
    expected = %w"1 1 1 2 2 2 3 3 1 4 4 2"
    assert_in_out_err(["--enable=gems", "-", @t1.path, @t2.path], "#{<<~"{#"}\n#{<<~'};'}", expected)
    #!/usr/bin/env ruby
    {#
      ARGF.each do |line|
        puts [$., ARGF.lineno, ARGF.file.lineno]
      end
    };
  end

  def test_new_lineno_each
    f = ARGF.class.new(@t1.path, @t2.path, @t3.path)
    result = []
    f.each {|line| result << [f.lineno, line]; break if result.size == 3}
    assert_equal(3, f.lineno)
    assert_equal((1..3).map {|i| [i, "#{i}\n"]}, result)

    f.rewind
    assert_equal(2, f.lineno)
  ensure
    f.close
  end

  def test_new_lineno_each_char
    f = ARGF.class.new(@t1.path, @t2.path, @t3.path)
    f.each_char.to_a
    assert_equal(0, f.lineno)
  ensure
    f.close
  end

  def test_inplace
    assert_in_out_err(["-", @t1.path, @t2.path, @t3.path],
                      "#{<<~"{#"}\n#{<<~'};'}")
    {#
      ARGF.inplace_mode = '.bak'
      while line = ARGF.gets
        puts line.chomp + '.new'
      end
    };
    assert_equal("1.new\n2.new\n", File.read(@t1.path))
    assert_equal("3.new\n4.new\n", File.read(@t2.path))
    assert_equal("5.new\n6.new\n", File.read(@t3.path))
    assert_equal("1\n2\n", File.read(@t1.path + ".bak"))
    assert_equal("3\n4\n", File.read(@t2.path + ".bak"))
    assert_equal("5\n6\n", File.read(@t3.path + ".bak"))
  end

  def test_inplace2
    assert_in_out_err(["-", @t1.path, @t2.path, @t3.path],
                      "#{<<~"{#"}\n#{<<~'};'}")
    {#
      ARGF.inplace_mode = '.bak'
      puts ARGF.gets.chomp + '.new'
      puts ARGF.gets.chomp + '.new'
      p ARGF.inplace_mode
      ARGF.inplace_mode = nil
      puts ARGF.gets.chomp + '.new'
      puts ARGF.gets.chomp + '.new'
      p ARGF.inplace_mode
      ARGF.inplace_mode = '.bak'
      puts ARGF.gets.chomp + '.new'
      p ARGF.inplace_mode
      ARGF.inplace_mode = nil
      puts ARGF.gets.chomp + '.new'
    };
    assert_equal("1.new\n2.new\n\".bak\"\n3.new\n4.new\nnil\n", File.read(@t1.path))
    assert_equal("3\n4\n", File.read(@t2.path))
    assert_equal("5.new\n\".bak\"\n6.new\n", File.read(@t3.path))
    assert_equal("1\n2\n", File.read(@t1.path + ".bak"))
    assert_equal(false, File.file?(@t2.path + ".bak"))
    assert_equal("5\n6\n", File.read(@t3.path + ".bak"))
  end

  def test_inplace3
    assert_in_out_err(["-i.bak", "-", @t1.path, @t2.path, @t3.path],
                      "#{<<~"{#"}\n#{<<~'};'}")
    {#
      puts ARGF.gets.chomp + '.new'
      puts ARGF.gets.chomp + '.new'
      p $-i
      $-i = nil
      puts ARGF.gets.chomp + '.new'
      puts ARGF.gets.chomp + '.new'
      p $-i
      $-i = '.bak'
      puts ARGF.gets.chomp + '.new'
      p $-i
      $-i = nil
      puts ARGF.gets.chomp + '.new'
    };
    assert_equal("1.new\n2.new\n\".bak\"\n3.new\n4.new\nnil\n", File.read(@t1.path))
    assert_equal("3\n4\n", File.read(@t2.path))
    assert_equal("5.new\n\".bak\"\n6.new\n", File.read(@t3.path))
    assert_equal("1\n2\n", File.read(@t1.path + ".bak"))
    assert_equal(false, File.file?(@t2.path + ".bak"))
    assert_equal("5\n6\n", File.read(@t3.path + ".bak"))
  end

  def test_inplace_rename_impossible
    t = make_tempfile

    assert_in_out_err(["-", t.path], "#{<<~"{#"}\n#{<<~'};'}") do |r, e|
      {#
        ARGF.inplace_mode = '/\\\\:'
        while line = ARGF.gets
          puts line.chomp + '.new'
        end
      };
      assert_match(/Can't rename .* to .*: .*. skipping file/, e.first) #'
      assert_equal([], r)
      assert_equal("foo\nbar\nbaz\n", File.read(t.path))
    end

    base = "argf-\u{30c6 30b9 30c8}"
    name = "#{@tmpdir}/#{base}"
    File.write(name, "foo")
    argf = ARGF.class.new(name)
    argf.inplace_mode = '/\\:'
    assert_warning(/#{base}/) {argf.gets}
  end

  def test_inplace_nonascii
    ext = Encoding.default_external or
      omit "no default external encoding"
    t = nil
    ["\u{3042}", "\u{e9}"].any? do |n|
      t = make_tempfile(n.encode(ext))
    rescue Encoding::UndefinedConversionError
    end
    t or omit "no name to test"
    assert_in_out_err(["-i.bak", "-", t.path],
                      "#{<<~"{#"}\n#{<<~'};'}")
    {#
      puts ARGF.gets.chomp + '.new'
      puts ARGF.gets.chomp + '.new'
      puts ARGF.gets.chomp + '.new'
    };
    assert_equal("foo.new\n""bar.new\n""baz.new\n", File.read(t.path))
    assert_equal("foo\n""bar\n""baz\n", File.read(t.path + ".bak"))
  end

  def test_inplace_no_backup
    t = make_tempfile

    assert_in_out_err(["-", t.path], "#{<<~"{#"}\n#{<<~'};'}") do |r, e|
      {#
        ARGF.inplace_mode = ''
        while line = ARGF.gets
          puts line.chomp + '.new'
        end
      };
      if no_safe_rename
        assert_match(/Can't do inplace edit without backup/, e.join) #'
      else
        assert_equal([], e)
        assert_equal([], r)
        assert_equal("foo.new\nbar.new\nbaz.new\n", File.read(t.path))
      end
    end
  end

  def test_inplace_dup
    t = make_tempfile

    assert_in_out_err(["-", t.path], "#{<<~"{#"}\n#{<<~'};'}", [], [])
    {#
      ARGF.inplace_mode = '.bak'
      f = ARGF.dup
      while line = f.gets
        puts line.chomp + '.new'
      end
    };
    assert_equal("foo.new\nbar.new\nbaz.new\n", File.read(t.path))
  end

  def test_inplace_stdin
    assert_in_out_err(["-", "-"], "#{<<~"{#"}\n#{<<~'};'}", [], /Can't do inplace edit for stdio; skipping/)
    {#
      ARGF.inplace_mode = '.bak'
      f = ARGF.dup
      while line = f.gets
        puts line.chomp + '.new'
      end
    };
  end

  def test_inplace_stdin2
    assert_in_out_err(["-"], "#{<<~"{#"}\n#{<<~'};'}", [], /Can't do inplace edit for stdio/)
    {#
      ARGF.inplace_mode = '.bak'
      while line = ARGF.gets
        puts line.chomp + '.new'
      end
    };
  end

  def test_inplace_invalid_backup
    assert_raise(ArgumentError, '[ruby-dev:50272] [Bug #13960]') {
      ARGF.inplace_mode = "a\0"
    }
  end

  def test_inplace_to_path
    base = "argf-test"
    name = "#{@tmpdir}/#{base}"
    File.write(name, "foo")
    stdout = $stdout
    argf = ARGF.class.new(Struct.new(:to_path).new(name))
    begin
      result = argf.gets
    ensure
      $stdout = stdout
      argf.close
    end
    assert_equal("foo", result)
  end

  def test_inplace_ascii_incompatible_path
    base = "argf-\u{30c6 30b9 30c8}"
    name = "#{@tmpdir}/#{base}"
    File.write(name, "foo")
    stdout = $stdout
    argf = ARGF.class.new(name.encode(Encoding::UTF_16LE))
    assert_raise(Encoding::CompatibilityError) do
      argf.gets
    end
  ensure
    $stdout = stdout
  end

  def test_inplace_suffix_encoding
    base = "argf-\u{30c6 30b9 30c8}"
    name = "#{@tmpdir}/#{base}"
    suffix = "-bak"
    File.write(name, "foo")
    stdout = $stdout
    argf = ARGF.class.new(name)
    argf.inplace_mode = suffix.encode(Encoding::UTF_16LE)
    begin
      argf.each do |s|
        puts "+"+s
      end
    ensure
      $stdout.close unless $stdout == stdout
      $stdout = stdout
    end
    assert_file.exist?(name)
    assert_equal("+foo\n", File.read(name))
    assert_file.not_exist?(name+"-")
    assert_file.exist?(name+suffix)
    assert_equal("foo", File.read(name+suffix))
  end

  def test_inplace_bug_17117
    assert_in_out_err(["-", @t1.path], "#{<<~"{#"}#{<<~'};'}")
    {#
      #!/usr/bin/ruby -pi.bak
      BEGIN {
        GC.start
        arr = []
        1000000.times { |x| arr << "fooo#{x}" }
      }
      puts "hello"
    };
    assert_equal("hello\n1\nhello\n2\n", File.read(@t1.path))
    assert_equal("1\n2\n", File.read("#{@t1.path}.bak"))
  end

  def test_encoding
    ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
      {#
        p ARGF.external_encoding.is_a?(Encoding)
        p ARGF.internal_encoding.is_a?(Encoding)
        ARGF.gets
        p ARGF.external_encoding.is_a?(Encoding)
        p ARGF.internal_encoding
      };
      assert_equal("true\ntrue\ntrue\nnil\n", f.read)
    end
  end

  def test_tell
    ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
      {#
        begin
          ARGF.binmode
          loop do
            p ARGF.tell
            p ARGF.gets
          end
        rescue ArgumentError
          puts "end"
        end
      };
      a = f.read.split("\n")
      [0, 2, 4, 2, 4, 2, 4].map {|i| i.to_s }.
        zip((1..6).map {|i| '"' + i.to_s + '\n"' } + ["nil"]).flatten.
        each do |x|
        assert_equal(x, a.shift)
      end
      assert_equal('end', a.shift)
    end
  end

  def test_seek
    assert_src_expected("#{<<~"{#"}\n#{<<~'};'}")
    {#
      ARGF.seek(4)
      p ARGF.gets #=> "3\n"
      ARGF.seek(0, IO::SEEK_END)
      p ARGF.gets #=> "5\n"
      ARGF.seek(4)
      p ARGF.gets #=> nil
      begin
        ARGF.seek(0)
      rescue
        puts "end" #=> end
      end
    };
  end

  def test_set_pos
    assert_src_expected("#{<<~"{#"}\n#{<<~'};'}")
    {#
      ARGF.pos = 4
      p ARGF.gets #=> "3\n"
      ARGF.pos = 4
      p ARGF.gets #=> "5\n"
      ARGF.pos = 4
      p ARGF.gets #=> nil
      begin
        ARGF.pos = 4
      rescue
        puts "end" #=> end
      end
    };
  end

  def test_rewind
    assert_src_expected("#{<<~"{#"}\n#{<<~'};'}")
    {#
      ARGF.pos = 4
      ARGF.rewind
      p ARGF.gets #=> "1\n"
      ARGF.pos = 4
      p ARGF.gets #=> "3\n"
      ARGF.pos = 4
      p ARGF.gets #=> "5\n"
      ARGF.pos = 4
      p ARGF.gets #=> nil
      begin
        ARGF.rewind
      rescue
        puts "end" #=> end
      end
    };
  end

  def test_fileno
    ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
      {#
        p ARGF.fileno
        ARGF.gets
        ARGF.gets
        p ARGF.fileno
        ARGF.gets
        ARGF.gets
        p ARGF.fileno
        ARGF.gets
        ARGF.gets
        p ARGF.fileno
        ARGF.gets
        begin
          ARGF.fileno
        rescue
          puts "end"
        end
      };
      a = f.read.split("\n")
      fd1, fd2, fd3, fd4, tag = a
      assert_match(/^\d+$/, fd1)
      assert_match(/^\d+$/, fd2)
      assert_match(/^\d+$/, fd3)
      assert_match(/^\d+$/, fd4)
      assert_equal('end', tag)
    end
  end

  def test_to_io
    ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
      {#
        8.times do
          p ARGF.to_io
          ARGF.gets
        end
      };
      a = f.read.split("\n")
      f11, f12, f13, f21, f22, f31, f32, f4 = a
      assert_equal(f11, f12)
      assert_equal(f11, f13)
      assert_equal(f21, f22)
      assert_equal(f31, f32)
      assert_match(/\(closed\)/, f4)
      f4.sub!(/ \(closed\)/, "")
      assert_equal(f31, f4)
    end
  end

  def test_eof
    ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
      {#
        begin
          8.times do
            p ARGF.eof?
            ARGF.gets
          end
        rescue IOError
          puts "end"
        end
      };
      a = f.read.split("\n")
      (%w(false) + (%w(false true) * 3) + %w(end)).each do |x|
        assert_equal(x, a.shift)
      end
    end

    t1 = "#{@tmpdir}/argf-hoge"
    t2 = "#{@tmpdir}/argf-moge"
    File.binwrite(t1, "foo\n")
    File.binwrite(t2, "bar\n")
    ruby('-e', 'STDERR.reopen(STDOUT); ARGF.gets; ARGF.skip; p ARGF.eof?', t1, t2) do |f|
      assert_equal(%w(false), f.read.split(/\n/))
    end
  end

  def test_read
    ruby('-e', "p ARGF.read(8)", @t1.path, @t2.path, @t3.path) do |f|
      assert_equal("\"1\\n2\\n3\\n4\\n\"\n", f.read)
    end
  end

  def test_read2
    ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
      {#
        s = +""
        ARGF.read(8, s)
        p s
      };
      assert_equal("\"1\\n2\\n3\\n4\\n\"\n", f.read)
    end
  end

  def test_read2_with_not_empty_buffer
    ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
      {#
        s = +"0123456789"
        ARGF.read(8, s)
        p s
      };
      assert_equal("\"1\\n2\\n3\\n4\\n\"\n", f.read)
    end
  end

  def test_read3
    ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
      {#
        nil while ARGF.gets
        p ARGF.read
        p ARGF.read(0, +"")
      };
      assert_equal("nil\n\"\"\n", f.read)
    end
  end

  def test_readpartial
    ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
      {#
        s = +""
        begin
          loop do
            s << ARGF.readpartial(1)
            t = +""; ARGF.readpartial(1, t); s << t
            # not empty buffer
            u = +"abcdef"; ARGF.readpartial(1, u); s << u
          end
        rescue EOFError
          puts s
        end
      };
      assert_equal("1\n2\n3\n4\n5\n6\n", f.read)
    end
  end

  def test_readpartial2
    ruby('-e', "#{<<~"{#"}\n#{<<~'};'}") do |f|
      {#
        s = +""
        begin
          loop do
            s << ARGF.readpartial(1)
            t = +""; ARGF.readpartial(1, t); s << t
          end
        rescue EOFError
          $stdout.binmode
          puts s
        end
      };
      f.binmode
      f.puts("foo")
      f.puts("bar")
      f.puts("baz")
      f.close_write
      assert_equal("foo\nbar\nbaz\n", f.read)
    end
  end

  def test_readpartial_eof_twice
    ruby('-W1', '-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path) do |f|
      {#
        $stderr = $stdout
        print ARGF.readpartial(256)
        ARGF.readpartial(256) rescue p($!.class)
        ARGF.readpartial(256) rescue p($!.class)
      };
      assert_equal("1\n2\nEOFError\nEOFError\n", f.read)
    end
  end

  def test_getc
    ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
      {#
        s = +""
        while c = ARGF.getc
          s << c
        end
        puts s
      };
      assert_equal("1\n2\n3\n4\n5\n6\n", f.read)
    end
  end

  def test_getbyte
    ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
      {#
        s = []
        while c = ARGF.getbyte
          s << c
        end
        p s
      };
      assert_equal("[49, 10, 50, 10, 51, 10, 52, 10, 53, 10, 54, 10]\n", f.read)
    end
  end

  def test_readchar
    ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
      {#
        s = +""
        begin
          while c = ARGF.readchar
            s << c
          end
        rescue EOFError
          puts s
        end
      };
      assert_equal("1\n2\n3\n4\n5\n6\n", f.read)
    end
  end

  def test_readbyte
    ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
      {#
        begin
          s = []
          while c = ARGF.readbyte
            s << c
          end
        rescue EOFError
          p s
        end
      };
      assert_equal("[49, 10, 50, 10, 51, 10, 52, 10, 53, 10, 54, 10]\n", f.read)
    end
  end

  def test_each_line
    ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
      {#
        s = []
        ARGF.each_line {|l| s << l }
        p s
      };
      assert_equal("[\"1\\n\", \"2\\n\", \"3\\n\", \"4\\n\", \"5\\n\", \"6\\n\"]\n", f.read)
    end
  end

  def test_each_line_paragraph
    assert_in_out_err(['-e', 'ARGF.each_line("") {|para| p para}'], "a\n\nb\n",
                      ["\"a\\n\\n\"", "\"b\\n\""], [])
  end

  def test_each_line_chomp
    assert_in_out_err(['-e', 'ARGF.each_line(chomp: false) {|para| p para}'], "a\nb\n",
                      ["\"a\\n\"", "\"b\\n\""], [])
    assert_in_out_err(['-e', 'ARGF.each_line(chomp: true) {|para| p para}'], "a\nb\n",
                      ["\"a\"", "\"b\""], [])

    t = make_tempfile
    argf = ARGF.class.new(t.path)
    lines = []
    begin
      argf.each_line(chomp: true) do |line|
        lines << line
      end
    ensure
      argf.close
    end
    assert_equal(%w[foo bar baz], lines)
  end

  def test_each_byte
    ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
      {#
        s = []
        ARGF.each_byte {|c| s << c }
        p s
      };
      assert_equal("[49, 10, 50, 10, 51, 10, 52, 10, 53, 10, 54, 10]\n", f.read)
    end
  end

  def test_each_char
    ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
      {#
        s = +""
        ARGF.each_char {|c| s << c }
        puts s
      };
      assert_equal("1\n2\n3\n4\n5\n6\n", f.read)
    end
  end

  def test_filename
    ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
      {#
        begin
          puts ARGF.filename.dump
        end while ARGF.gets
        puts ARGF.filename.dump
      };
      a = f.read.split("\n")
      assert_equal(@t1.path.dump, a.shift)
      assert_equal(@t1.path.dump, a.shift)
      assert_equal(@t1.path.dump, a.shift)
      assert_equal(@t2.path.dump, a.shift)
      assert_equal(@t2.path.dump, a.shift)
      assert_equal(@t3.path.dump, a.shift)
      assert_equal(@t3.path.dump, a.shift)
      assert_equal(@t3.path.dump, a.shift)
    end
  end

  def test_filename2
    ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
      {#
        begin
          puts $FILENAME.dump
        end while ARGF.gets
        puts $FILENAME.dump
      };
      a = f.read.split("\n")
      assert_equal(@t1.path.dump, a.shift)
      assert_equal(@t1.path.dump, a.shift)
      assert_equal(@t1.path.dump, a.shift)
      assert_equal(@t2.path.dump, a.shift)
      assert_equal(@t2.path.dump, a.shift)
      assert_equal(@t3.path.dump, a.shift)
      assert_equal(@t3.path.dump, a.shift)
      assert_equal(@t3.path.dump, a.shift)
    end
  end

  def test_file
    ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
      {#
        begin
          puts ARGF.file.path.dump
        end while ARGF.gets
        puts ARGF.file.path.dump
      };
      a = f.read.split("\n")
      assert_equal(@t1.path.dump, a.shift)
      assert_equal(@t1.path.dump, a.shift)
      assert_equal(@t1.path.dump, a.shift)
      assert_equal(@t2.path.dump, a.shift)
      assert_equal(@t2.path.dump, a.shift)
      assert_equal(@t3.path.dump, a.shift)
      assert_equal(@t3.path.dump, a.shift)
      assert_equal(@t3.path.dump, a.shift)
    end
  end

  def test_binmode
    bug5268 = '[ruby-core:39234]'
    File.binwrite(@t3.path, "5\r\n6\r\n")
    ruby('-e', "ARGF.binmode; STDOUT.binmode; puts ARGF.read", @t1.path, @t2.path, @t3.path) do |f|
      f.binmode
      assert_equal("1\n2\n3\n4\n5\r\n6\r\n", f.read, bug5268)
    end
  end

  def test_textmode
    bug5268 = '[ruby-core:39234]'
    File.binwrite(@t3.path, "5\r\n6\r\n")
    ruby('-e', "STDOUT.binmode; puts ARGF.read", @t1.path, @t2.path, @t3.path) do |f|
      f.binmode
      assert_equal("1\n2\n3\n4\n5\n6\n", f.read, bug5268)
    end
  end unless IO::BINARY.zero?

  def test_skip
    ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
      {#
        ARGF.skip
        puts ARGF.gets
        ARGF.skip
        puts ARGF.read
      };
      assert_equal("1\n3\n4\n5\n6\n", f.read)
    end
  end

  def test_skip_in_each_line
    ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
      {#
        ARGF.each_line {|l| print l; ARGF.skip}
      };
      assert_equal("1\n3\n5\n", f.read, '[ruby-list:49185]')
    end
    ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
      {#
        ARGF.each_line {|l| ARGF.skip; puts [l, ARGF.gets].map {|s| s ? s.chomp : s.inspect}.join("+")}
      };
      assert_equal("1+3\n4+5\n6+nil\n", f.read, '[ruby-list:49185]')
    end
  end

  def test_skip_in_each_byte
    ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
      {#
        ARGF.each_byte {|l| print l; ARGF.skip}
      };
      assert_equal("135".unpack("C*").join(""), f.read, '[ruby-list:49185]')
    end
  end

  def test_skip_in_each_char
    [[@t1, "\u{3042}"], [@t2, "\u{3044}"], [@t3, "\u{3046}"]].each do |f, s|
      File.write(f.path, s, mode: "w:utf-8")
    end
    ruby('-Eutf-8', '-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
      {#
        ARGF.each_char {|l| print l; ARGF.skip}
      };
      assert_equal("\u{3042 3044 3046}", f.read, '[ruby-list:49185]')
    end
  end

  def test_skip_in_each_codepoint
    [[@t1, "\u{3042}"], [@t2, "\u{3044}"], [@t3, "\u{3046}"]].each do |f, s|
      File.write(f.path, s, mode: "w:utf-8")
    end
    ruby('-Eutf-8', '-Eutf-8', '-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
      {#
        ARGF.each_codepoint {|l| printf "%x:", l; ARGF.skip}
      };
      assert_equal("3042:3044:3046:", f.read, '[ruby-list:49185]')
    end
  end

  def test_close
    ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
      {#
        ARGF.close
        puts ARGF.read
      };
      assert_equal("3\n4\n5\n6\n", f.read)
    end
  end

  def test_close_replace
    ruby('-e', "#{<<~"{#"}\n#{<<~'};'}") do |f|
      paths = ['#{@t1.path}', '#{@t2.path}', '#{@t3.path}']
      {#
        ARGF.close
        ARGV.replace paths
        puts ARGF.read
      };
      assert_equal("1\n2\n3\n4\n5\n6\n", f.read)
    end
  end

  def test_closed
    ruby('-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
      {#
        3.times do
          p ARGF.closed?
          ARGF.gets
          ARGF.gets
        end
        p ARGF.closed?
        ARGF.gets
        p ARGF.closed?
      };
      assert_equal("false\nfalse\nfalse\nfalse\ntrue\n", f.read)
    end
  end

  def test_argv
    ruby('-e', "p ARGF.argv; p $*", @t1.path, @t2.path, @t3.path) do |f|
      assert_equal([@t1.path, @t2.path, @t3.path].inspect, f.gets.chomp)
      assert_equal([@t1.path, @t2.path, @t3.path].inspect, f.gets.chomp)
    end
  end

  def test_readlines_limit_0
    bug4024 = '[ruby-dev:42538]'
    t = make_tempfile
    argf = ARGF.class.new(t.path)
    begin
      assert_raise(ArgumentError, bug4024) do
        argf.readlines(0)
      end
    ensure
      argf.close
    end
  end

  def test_each_line_limit_0
    bug4024 = '[ruby-dev:42538]'
    t = make_tempfile
    argf = ARGF.class.new(t.path)
    begin
      assert_raise(ArgumentError, bug4024) do
        argf.each_line(0).next
      end
    ensure
      argf.close
    end
  end

  def test_unreadable
    bug4274 = '[ruby-core:34446]'
    paths = (1..2).map do
      t = Tempfile.new("bug4274-")
      path = t.path
      t.close!
      path
    end
    argf = ARGF.class.new(*paths)
    paths.each do |path|
      assert_raise_with_message(Errno::ENOENT, /- #{Regexp.quote(path)}\z/) {argf.gets}
    end
    assert_nil(argf.gets, bug4274)
  end

  def test_readlines_chomp
    t = make_tempfile
    argf = ARGF.class.new(t.path)
    begin
      assert_equal(%w[foo bar baz], argf.readlines(chomp: true))
    ensure
      argf.close
    end

    assert_in_out_err(['-e', 'p readlines(chomp: true)'], "a\nb\n",
                      ["[\"a\", \"b\"]"], [])
  end

  def test_readline_chomp
    t = make_tempfile
    argf = ARGF.class.new(t.path)
    begin
      assert_equal("foo", argf.readline(chomp: true))
    ensure
      argf.close
    end

    assert_in_out_err(['-e', 'p readline(chomp: true)'], "a\nb\n",
                      ["\"a\""], [])
  end

  def test_gets_chomp
    t = make_tempfile
    argf = ARGF.class.new(t.path)
    begin
      assert_equal("foo", argf.gets(chomp: true))
    ensure
      argf.close
    end

    assert_in_out_err(['-e', 'p gets(chomp: true)'], "a\nb\n",
                      ["\"a\""], [])
  end

  def test_readlines_twice
    bug5952 = '[ruby-dev:45160]'
    assert_ruby_status(["-e", "2.times {STDIN.tty?; readlines}"], "", bug5952)
  end

  def test_each_codepoint
    ruby('-W1', '-e', "#{<<~"{#"}\n#{<<~'};'}", @t1.path, @t2.path, @t3.path) do |f|
      {#
        print Marshal.dump(ARGF.each_codepoint.to_a)
      };
      assert_equal([49, 10, 50, 10, 51, 10, 52, 10, 53, 10, 54, 10], Marshal.load(f.read))
    end
  end

  def test_read_nonblock
    ruby('-e', "#{<<~"{#"}\n#{<<~'};'}") do |f|
      {#
        $stdout.sync = true
        :wait_readable == ARGF.read_nonblock(1, +"", exception: false) or
          abort "did not return :wait_readable"

        begin
          ARGF.read_nonblock(1)
          abort 'fail to raise IO::WaitReadable'
        rescue IO::WaitReadable
        end
        puts 'starting select'

        IO.select([ARGF]) == [[ARGF], [], []] or
          abort 'did not awaken for readability (before byte)'

        buf = +''
        buf.object_id == ARGF.read_nonblock(1, buf).object_id or
          abort "read destination buffer failed"
        print buf

        IO.select([ARGF]) == [[ARGF], [], []] or
          abort 'did not awaken for readability (before EOF)'

        ARGF.read_nonblock(1, buf, exception: false) == nil or
          abort "EOF should return nil if exception: false"

        begin
          ARGF.read_nonblock(1, buf)
          abort 'fail to raise IO::WaitReadable'
        rescue EOFError
          puts 'done with eof'
        end
      };
      f.sync = true
      assert_equal "starting select\n", f.gets
      f.write('.') # wake up from IO.select
      assert_equal '.', f.read(1)
      f.close_write
      assert_equal "done with eof\n", f.gets
    end
  end

  def test_wrong_type
    assert_separately([], "#{<<~"{#"}\n#{<<~'};'}")
    {#
      bug11610 = '[ruby-core:71140] [Bug #11610]'
      ARGV[0] = nil
      assert_raise(TypeError, bug11610) {gets}
    };
  end

  def test_sized_read
    s = "a"
    [@t1, @t2, @t3].each { |t|
      File.binwrite(t.path, s)
      s = s.succ
    }

    ruby('-e', "print ARGF.read(3)", @t1.path, @t2.path, @t3.path) do |f|
      assert_equal("abc", f.read)
    end

    argf = ARGF.class.new(@t1.path, @t2.path, @t3.path)
    begin
      assert_equal("abc", argf.read(3))
    ensure
      argf.close
    end
  end

  def test_putc
    t = make_tempfile("argf-#{__method__}", 'bar')
    ruby('-pi-', '-e', "print ARGF.putc('x')", t.path) do |f|
    end
    assert_equal("xxbar\n", File.read(t.path))
  end

  def test_puts
    t = make_tempfile("argf-#{__method__}", 'bar')
    err = "#{@tmpdir}/errout"
    ruby('-pi-', '-W2', '-e', "print ARGF.puts('foo')", t.path, {err: err}) do |f|
    end
    assert_equal("foo\nbar\n", File.read(t.path))
    assert_empty File.read(err)
  end

  def test_print
    t = make_tempfile("argf-#{__method__}", 'bar')
    ruby('-pi-', '-e', "print ARGF.print('foo')", t.path) do |f|
    end
    assert_equal("foobar\n", File.read(t.path))
  end

  def test_printf
    t = make_tempfile("argf-#{__method__}", 'bar')
    ruby('-pi-', '-e', "print ARGF.printf('%s', 'foo')", t.path) do |f|
    end
    assert_equal("foobar\n", File.read(t.path))
  end
end
