require 'test/unit'
require_relative 'envutil'

class TestEnv < Test::Unit::TestCase
  IGNORE_CASE = /bccwin|mswin|mingw/ =~ RUBY_PLATFORM
  PATH_ENV = "PATH"

  def setup
    @verbose = $VERBOSE
    $VERBOSE = nil
    @backup = ENV.to_hash
    ENV.delete('test')
    ENV.delete('TEST')
  end

  def teardown
    $VERBOSE = @verbose
    ENV.clear
    @backup.each {|k, v| ENV[k] = v }
  end

  def test_bracket
    assert_nil(ENV['test'])
    assert_nil(ENV['TEST'])
    ENV['test'] = 'foo'
    assert_equal('foo', ENV['test'])
    if IGNORE_CASE
      assert_equal('foo', ENV['TEST'])
    else
      assert_nil(ENV['TEST'])
    end
    ENV['TEST'] = 'bar'
    assert_equal('bar', ENV['TEST'])
    if IGNORE_CASE
      assert_equal('bar', ENV['test'])
    else
      assert_equal('foo', ENV['test'])
    end

    assert_raise(TypeError) {
      tmp = ENV[1]
    }
    assert_raise(TypeError) {
      ENV[1] = 'foo'
    }
    assert_raise(TypeError) {
      ENV['test'] = 0
    }
  end

  def test_has_value
    val = 'a'
    val.succ! while ENV.has_value?(val) || ENV.has_value?(val.upcase)
    ENV['test'] = val[0...-1]

    assert_equal(false, ENV.has_value?(val))
    assert_equal(false, ENV.has_value?(val.upcase))
    ENV['test'] = val
    assert_equal(true, ENV.has_value?(val))
    assert_equal(false, ENV.has_value?(val.upcase))
    ENV['test'] = val.upcase
    assert_equal(false, ENV.has_value?(val))
    assert_equal(true, ENV.has_value?(val.upcase))
  end

  def test_key
    val = 'a'
    val.succ! while ENV.has_value?(val) || ENV.has_value?(val.upcase)
    ENV['test'] = val[0...-1]

    assert_nil(ENV.key(val))
    assert_nil(ENV.index(val))
    assert_nil(ENV.key(val.upcase))
    ENV['test'] = val
    if IGNORE_CASE
      assert_equal('TEST', ENV.key(val).upcase)
    else
      assert_equal('test', ENV.key(val))
    end
    assert_nil(ENV.key(val.upcase))
    ENV['test'] = val.upcase
    assert_nil(ENV.key(val))
    if IGNORE_CASE
      assert_equal('TEST', ENV.key(val.upcase).upcase)
    else
      assert_equal('test', ENV.key(val.upcase))
    end
  end

  def test_delete
    assert_raise(ArgumentError) { ENV.delete("foo\0bar") }
    assert_nil(ENV.delete("TEST"))
    assert_nothing_raised { ENV.delete(PATH_ENV) }
  end

  def test_getenv
    assert_raise(ArgumentError) { ENV["foo\0bar"] }
    ENV[PATH_ENV] = ""
    assert_equal("", ENV[PATH_ENV])
    assert_nil(ENV[""])
  end

  def test_fetch
    ENV["test"] = "foo"
    assert_equal("foo", ENV.fetch("test"))
    ENV.delete("test")
    assert_raise(KeyError) { ENV.fetch("test") }
    assert_equal("foo", ENV.fetch("test", "foo"))
    assert_equal("bar", ENV.fetch("test") { "bar" })
    assert_equal("bar", ENV.fetch("test", "foo") { "bar" })
    assert_raise(ArgumentError) { ENV.fetch("foo\0bar") }
    assert_nothing_raised { ENV.fetch(PATH_ENV, "foo") }
    ENV[PATH_ENV] = ""
    assert_equal("", ENV.fetch(PATH_ENV))
  end

  def test_aset
    assert_raise(SecurityError) do
      Thread.new do
        $SAFE = 4
        ENV["test"] = "foo"
      end.join
    end
    assert_nothing_raised { ENV["test"] = nil }
    assert_equal(nil, ENV["test"])
    assert_raise(ArgumentError) { ENV["foo\0bar"] = "test" }
    assert_raise(ArgumentError) { ENV["test"] = "foo\0bar" }

    begin
      # setenv(3) allowed the name includes '=',
      # but POSIX.1-2001 says it should fail with EINVAL.
      # see also http://togetter.com/li/22380
      ENV["foo=bar"] = "test"
      assert_equal("test", ENV["foo=bar"])
      assert_equal("test", ENV["foo"])
    rescue Errno::EINVAL
    end

    ENV[PATH_ENV] = "/tmp/".taint
    assert_equal("/tmp/", ENV[PATH_ENV])
  end

  def test_keys
    a = ENV.keys
    assert_kind_of(Array, a)
    a.each {|k| assert_kind_of(String, k) }
  end

  def test_each_key
    ENV.each_key {|k| assert_kind_of(String, k) }
  end

  def test_values
    a = ENV.values
    assert_kind_of(Array, a)
    a.each {|k| assert_kind_of(String, k) }
  end

  def test_each_value
    ENV.each_value {|k| assert_kind_of(String, k) }
  end

  def test_each_pair
    ENV.each_pair do |k, v|
      assert_kind_of(String, k)
      assert_kind_of(String, v)
    end
  end

  def test_reject_bang
    h1 = {}
    ENV.each_pair {|k, v| h1[k] = v }
    ENV["test"] = "foo"
    ENV.reject! {|k, v| IGNORE_CASE ? k.upcase == "TEST" : k == "test" }
    h2 = {}
    ENV.each_pair {|k, v| h2[k] = v }
    assert_equal(h1, h2)

    h1 = {}
    ENV.each_pair {|k, v| h1[k] = v }
    ENV["test"] = "foo"
    ENV.delete_if {|k, v| IGNORE_CASE ? k.upcase == "TEST" : k == "test" }
    h2 = {}
    ENV.each_pair {|k, v| h2[k] = v }
    assert_equal(h1, h2)
  end

  def test_select_bang
    h1 = {}
    ENV.each_pair {|k, v| h1[k] = v }
    ENV["test"] = "foo"
    ENV.select! {|k, v| IGNORE_CASE ? k.upcase != "TEST" : k != "test" }
    h2 = {}
    ENV.each_pair {|k, v| h2[k] = v }
    assert_equal(h1, h2)

    h1 = {}
    ENV.each_pair {|k, v| h1[k] = v }
    ENV["test"] = "foo"
    ENV.keep_if {|k, v| IGNORE_CASE ? k.upcase != "TEST" : k != "test" }
    h2 = {}
    ENV.each_pair {|k, v| h2[k] = v }
    assert_equal(h1, h2)
  end

  def test_values_at
    ENV["test"] = "foo"
    assert_equal(["foo", "foo"], ENV.values_at("test", "test"))
  end

  def test_select
    ENV["test"] = "foo"
    h = ENV.select {|k| IGNORE_CASE ? k.upcase == "TEST" : k == "test" }
    assert_equal(1, h.size)
    k = h.keys.first
    v = h.values.first
    if IGNORE_CASE
      assert_equal("TEST", k.upcase)
      assert_equal("FOO", v.upcase)
    else
      assert_equal("test", k)
      assert_equal("foo", v)
    end
  end

  def test_clear
    ENV.clear
    assert_equal(0, ENV.size)
  end

  def test_to_s
    assert_equal("ENV", ENV.to_s)
  end

  def test_inspect
    ENV.clear
    ENV["foo"] = "bar"
    ENV["baz"] = "qux"
    s = ENV.inspect
    if IGNORE_CASE
      s = s.upcase
      assert(s == '{"FOO"=>"BAR", "BAZ"=>"QUX"}' || s == '{"BAZ"=>"QUX", "FOO"=>"BAR"}')
    else
      assert(s == '{"foo"=>"bar", "baz"=>"qux"}' || s == '{"baz"=>"qux", "foo"=>"bar"}')
    end
  end

  def test_to_a
    ENV.clear
    ENV["foo"] = "bar"
    ENV["baz"] = "qux"
    a = ENV.to_a
    assert_equal(2, a.size)
    if IGNORE_CASE
      a = a.map {|x| x.map {|y| y.upcase } }
      assert(a == [%w(FOO BAR), %w(BAZ QUX)] || a == [%w(BAZ QUX), %w(FOO BAR)])
    else
      assert(a == [%w(foo bar), %w(baz qux)] || a == [%w(baz qux), %w(foo bar)])
    end
  end

  def test_rehash
    assert_nil(ENV.rehash)
  end

  def test_size
    s = ENV.size
    ENV["test"] = "foo"
    assert_equal(s + 1, ENV.size)
  end

  def test_empty_p
    ENV.clear
    assert(ENV.empty?)
    ENV["test"] = "foo"
    assert(!ENV.empty?)
  end

  def test_has_key
    assert(!ENV.has_key?("test"))
    ENV["test"] = "foo"
    assert(ENV.has_key?("test"))
    assert_raise(ArgumentError) { ENV.has_key?("foo\0bar") }
  end

  def test_assoc
    assert_nil(ENV.assoc("test"))
    ENV["test"] = "foo"
    k, v = ENV.assoc("test")
    if IGNORE_CASE
      assert_equal("TEST", k.upcase)
      assert_equal("FOO", v.upcase)
    else
      assert_equal("test", k)
      assert_equal("foo", v)
    end
    assert_raise(ArgumentError) { ENV.assoc("foo\0bar") }
  end

  def test_has_value2
    ENV.clear
    assert(!ENV.has_value?("foo"))
    ENV["test"] = "foo"
    assert(ENV.has_value?("foo"))
  end

  def test_rassoc
    ENV.clear
    assert_nil(ENV.rassoc("foo"))
    ENV["foo"] = "bar"
    ENV["test"] = "foo"
    ENV["baz"] = "qux"
    k, v = ENV.rassoc("foo")
    if IGNORE_CASE
      assert_equal("TEST", k.upcase)
      assert_equal("FOO", v.upcase)
    else
      assert_equal("test", k)
      assert_equal("foo", v)
    end
  end

  def test_to_hash
    h = {}
    ENV.each {|k, v| h[k] = v }
    assert_equal(h, ENV.to_hash)
  end

  def test_to_h
    assert_equal(ENV.to_hash, ENV.to_h)
  end

  def test_reject
    h1 = {}
    ENV.each_pair {|k, v| h1[k] = v }
    ENV["test"] = "foo"
    h2 = ENV.reject {|k, v| IGNORE_CASE ? k.upcase == "TEST" : k == "test" }
    assert_equal(h1, h2)
  end

  def check(as, bs)
    if IGNORE_CASE
      as = as.map {|xs| xs.map {|x| x.upcase } }
      bs = bs.map {|xs| xs.map {|x| x.upcase } }
    end
    assert_equal(as.sort, bs.sort)
  end

  def test_shift
    ENV.clear
    ENV["foo"] = "bar"
    ENV["baz"] = "qux"
    a = ENV.shift
    b = ENV.shift
    check([a, b], [%w(foo bar), %w(baz qux)])
    assert_nil(ENV.shift)
  end

  def test_invert
    ENV.clear
    ENV["foo"] = "bar"
    ENV["baz"] = "qux"
    check(ENV.invert.to_a, [%w(bar foo), %w(qux baz)])
  end

  def test_replace
    ENV["foo"] = "xxx"
    ENV.replace({"foo"=>"bar", "baz"=>"qux"})
    check(ENV.to_hash.to_a, [%w(foo bar), %w(baz qux)])
  end

  def test_update
    ENV.clear
    ENV["foo"] = "bar"
    ENV["baz"] = "qux"
    ENV.update({"baz"=>"quux","a"=>"b"})
    check(ENV.to_hash.to_a, [%w(foo bar), %w(baz quux), %w(a b)])

    ENV.clear
    ENV["foo"] = "bar"
    ENV["baz"] = "qux"
    ENV.update({"baz"=>"quux","a"=>"b"}) {|k, v1, v2| v1 ? k + "_" + v1 + "_" + v2 : v2 }
    check(ENV.to_hash.to_a, [%w(foo bar), %w(baz baz_qux_quux), %w(a b)])
  end

  def test_huge_value
    huge_value = "bar" * 40960
    ENV["foo"] = "bar"
    if /mswin|mingw/ =~ RUBY_PLATFORM
      assert_raise(Errno::EINVAL) { ENV["foo"] = huge_value }
      assert_equal("bar", ENV["foo"])
    else
      assert_nothing_raised { ENV["foo"] = huge_value }
      assert_equal(huge_value, ENV["foo"])
    end
  end

  if /mswin|mingw/ =~ RUBY_PLATFORM
    def test_win32_blocksize
      len = 32767 - ENV.to_a.flatten.inject(0) {|r,e| r + e.size + 2 }
      val = "bar" * 1000
      key = nil
      1.upto(12) {|i|
        ENV[key] = val while (len -= val.size + (key="foo#{len}").size + 2) > 0
        assert_raise(Errno::EINVAL) { ENV[key] = val }
      }
    end
  end

  if RUBY_PLATFORM =~ /bccwin|mswin|mingw/
    def test_memory_leak_aset
      bug9977 = '[ruby-dev:48323] [Bug #9977]'
      assert_no_memory_leak([], <<-'end;', "5_000.times(&doit)", bug9977, limit: 2.0)
        ENV.clear
        k = 'FOO'
        v = (ENV[k] = 'bar'*5000 rescue 'bar'*1500)
        doit = proc {ENV[k] = v}
        500.times(&doit)
      end;
    end

    def test_memory_leak_select
      bug9978 = '[ruby-dev:48325] [Bug #9978]'
      assert_no_memory_leak([], <<-'end;', "5_000.times(&doit)", bug9978, limit: 2.0)
        ENV.clear
        k = 'FOO'
        (ENV[k] = 'bar'*5000 rescue 'bar'*1500)
        doit = proc {ENV.select {break}}
        500.times(&doit)
      end;
    end

    def test_memory_crash_select
      assert_normal_exit(<<-'end;')
        1000.times {ENV["FOO#{i}"] = 'bar'}
        ENV.select {ENV.clear}
      end;
    end

    def test_memory_leak_shift
      bug9983 = '[ruby-dev:48332] [Bug #9983]'
      assert_no_memory_leak([], <<-'end;', "5_000.times(&doit)", bug9983, limit: 2.0)
        ENV.clear
        k = 'FOO'
        v = (ENV[k] = 'bar'*5000 rescue 'bar'*1500)
        doit = proc {ENV[k] = v; ENV.shift}
        500.times(&doit)
      end;
    end
  end

  def test_taint_aref
    assert_raise(SecurityError) do
      proc do
        $SAFE = 2
        ENV["FOO".taint]
      end.call
    end
  end

  def test_taint_fetch
    assert_raise(SecurityError) do
      proc do
        $SAFE = 2
        ENV.fetch("FOO".taint)
      end.call
    end
  end

  def test_taint_assoc
    assert_raise(SecurityError) do
      proc do
        $SAFE = 2
        ENV.assoc("FOO".taint)
      end.call
    end
  end

  def test_taint_rassoc
    assert_raise(SecurityError) do
      proc do
        $SAFE = 2
        ENV.rassoc("FOO".taint)
      end.call
    end
  end

  def test_taint_key
    assert_raise(SecurityError) do
      proc do
        $SAFE = 2
        ENV.key("FOO".taint)
      end.call
    end
  end

  def test_taint_key_p
    assert_raise(SecurityError) do
      proc do
        $SAFE = 2
        ENV.key?("FOO".taint)
      end.call
    end
  end

  def test_taint_value_p
    assert_raise(SecurityError) do
      proc do
        $SAFE = 2
        ENV.value?("FOO".taint)
      end.call
    end
  end

  def test_taint_aset_value
    assert_raise(SecurityError) do
      proc do
        $SAFE = 2
        ENV["FOO"] = "BAR".taint
      end.call
    end
  end

  def test_taint_aset_key
    assert_raise(SecurityError) do
      proc do
        $SAFE = 2
        ENV["FOO".taint] = "BAR"
      end.call
    end
  end
end
