# -*- encoding: binary -*-
# frozen_string_literal: false

require 'test/unit'
require 'digest/sha1'
require 'unicorn'

class TestStreamInput < Test::Unit::TestCase
  def setup
    @rs = "\n"
    $/ == "\n" or abort %q{test broken if \$/ != "\\n"}
    @env = {}
    @rd, @wr = UNIXSocket.pair
    @rd.sync = @wr.sync = true
    @start_pid = $$
  end

  def teardown
    return if $$ != @start_pid
    @rd.close rescue nil
    @wr.close rescue nil
    Process.waitall
  end

  def test_read_negative
    r = init_request('hello')
    si = Unicorn::StreamInput.new(@rd, r)
    assert_raises(ArgumentError) { si.read(-1) }
    assert_equal 'hello', si.read
  end

  def test_read_small
    r = init_request('hello')
    si = Unicorn::StreamInput.new(@rd, r)
    assert_equal 'hello', si.read
    assert_equal '', si.read
    assert_nil si.read(5)
    assert_nil si.gets
  end

  def test_gets_oneliner
    r = init_request('hello')
    si = Unicorn::StreamInput.new(@rd, r)
    assert_equal 'hello', si.gets
    assert_nil si.gets
  end

  def test_gets_multiline
    r = init_request("a\nb\n\n")
    si = Unicorn::StreamInput.new(@rd, r)
    assert_equal "a\n", si.gets
    assert_equal "b\n", si.gets
    assert_equal "\n", si.gets
    assert_nil si.gets
  end

  def test_gets_empty_rs
    r = init_request("a\nb\n\n")
    si = Unicorn::StreamInput.new(@rd, r)
    pid = fork do # to avoid $/ warning (hopefully)
      $/ = nil
      @rd.close
      @wr.write(si.gets)
      @wr.close
    end
    @wr.close
    assert_equal "a\nb\n\n", @rd.read
    pid, status = Process.waitpid2(pid)
    assert_predicate status, :success?
  end

  def test_read_with_equal_len
    r = init_request("abcde")
    si = Unicorn::StreamInput.new(@rd, r)
    assert_equal "abcde", si.read(5)
    assert_nil si.read(5)
  end

  def test_big_body_multi
    r = init_request('.', Unicorn::Const::MAX_BODY + 1)
    si = Unicorn::StreamInput.new(@rd, r)
    assert_equal Unicorn::Const::MAX_BODY, @parser.content_length
    assert ! @parser.body_eof?
    nr = Unicorn::Const::MAX_BODY / 4
    pid = fork {
      @rd.close
      nr.times { @wr.write('....') }
      @wr.close
    }
    @wr.close
    assert_equal '.', si.read(1)
    nr.times { |x|
      assert_equal '....', si.read(4), "nr=#{x}"
    }
    assert_nil si.read(1)
    pid, status = Process.waitpid2(pid)
    assert status.success?
  end

  def test_gets_long
    r = init_request("hello", 5 + (4096 * 4 * 3) + "#{@rs}foo#{@rs}".size)
    si = Unicorn::StreamInput.new(@rd, r)
    status = line = nil
    pid = fork {
      @rd.close
      3.times { @wr.write("ffff" * 4096) }
      @wr.write "#{@rs}foo#{@rs}"
      @wr.close
    }
    @wr.close
    line = si.gets
    assert_equal(4096 * 4 * 3 + 5 + $/.size, line.size)
    assert_equal("hello" << ("ffff" * 4096 * 3) << "#{@rs}", line)
    line = si.gets
    assert_equal "foo#{@rs}", line
    assert_nil si.gets
    pid, status = Process.waitpid2(pid)
    assert status.success?
  end

  def test_read_with_buffer
    r = init_request('hello')
    si = Unicorn::StreamInput.new(@rd, r)
    buf = ''
    rv = si.read(4, buf)
    assert_equal 'hell', rv
    assert_equal 'hell', buf
    assert_equal rv.object_id, buf.object_id
    assert_equal 'o', si.read
    assert_equal nil, si.read(5, buf)
  end

  def test_read_with_buffer_clobbers
    r = init_request('hello')
    si = Unicorn::StreamInput.new(@rd, r)
    buf = 'foo'
    assert_equal 'hello', si.read(nil, buf)
    assert_equal 'hello', buf
    assert_equal '', si.read(nil, buf)
    assert_equal '', buf
    buf = 'asdf'
    assert_nil si.read(5, buf)
    assert_equal '', buf
  end

  def test_read_zero
    r = init_request('hello')
    si = Unicorn::StreamInput.new(@rd, r)
    assert_equal '', si.read(0)
    buf = 'asdf'
    rv = si.read(0, buf)
    assert_equal rv.object_id, buf.object_id
    assert_equal '', buf
    assert_equal 'hello', si.read
    assert_nil si.read(5)
    assert_equal '', si.read(0)
    buf = 'hello'
    rv = si.read(0, buf)
    assert_equal rv.object_id, buf.object_id
    assert_equal '', rv
  end

  def test_gets_read_mix
    r = init_request("hello\nasdfasdf")
    si = Unicorn::StreamInput.new(@rd, r)
    assert_equal "hello\n", si.gets
    assert_equal "asdfasdf", si.read(9)
    assert_nil si.read(9)
  end

  def test_gets_read_mix_chunked
    r = @parser = Unicorn::HttpParser.new
    body = "6\r\nhello"
    @buf = "POST / HTTP/1.1\r\n" \
           "Host: localhost\r\n" \
           "Transfer-Encoding: chunked\r\n" \
           "\r\n#{body}"
    assert_equal @env, @parser.headers(@env, @buf)
    assert_equal body, @buf
    si = Unicorn::StreamInput.new(@rd, r)
    @wr.syswrite "\n\r\n"
    assert_equal "hello\n", si.gets
    @wr.syswrite "8\r\nasdfasdf\r\n"
    assert_equal"asdfasdf", si.read(9) + si.read(9)
    @wr.syswrite "0\r\n\r\n"
    assert_nil si.read(9)
  end

  def test_gets_read_mix_big
    r = init_request("hello\n#{'.' * 65536}")
    si = Unicorn::StreamInput.new(@rd, r)
    assert_equal "hello\n", si.gets
    assert_equal '.' * 16384, si.read(16384)
    assert_equal '.' * 16383, si.read(16383)
    assert_equal '.' * 16384, si.read(16384)
    assert_equal '.' * 16385, si.read(16385)
    assert_nil si.gets
  end

  def init_request(body, size = nil)
    @parser = Unicorn::HttpParser.new
    body = body.to_s.freeze
    @buf = "POST / HTTP/1.1\r\n" \
           "Host: localhost\r\n" \
           "Content-Length: #{size || body.size}\r\n" \
           "\r\n#{body}"
    assert_equal @env, @parser.headers(@env, @buf)
    assert_equal body, @buf
    @parser
  end
end
