1# frozen_string_literal: false
2require 'test/unit'
3
4class TestEnv < Test::Unit::TestCase
5  IGNORE_CASE = /bccwin|mswin|mingw/ =~ RUBY_PLATFORM
6  PATH_ENV = "PATH"
7  INVALID_ENVVARS = [
8    "foo\0bar",
9    "\xa1\xa1".force_encoding(Encoding::UTF_16LE),
10    "foo".force_encoding(Encoding::ISO_2022_JP),
11  ]
12
13  def assert_invalid_env(msg = nil)
14    all_assertions(msg) do |a|
15      INVALID_ENVVARS.each do |v|
16        a.for(v) do
17          assert_raise(ArgumentError) {yield v}
18        end
19      end
20    end
21  end
22
23  def setup
24    @verbose = $VERBOSE
25    $VERBOSE = nil
26    @backup = ENV.to_hash
27    ENV.delete('test')
28    ENV.delete('TEST')
29  end
30
31  def teardown
32    $VERBOSE = @verbose
33    ENV.clear
34    @backup.each {|k, v| ENV[k] = v }
35  end
36
37  def test_bracket
38    assert_nil(ENV['test'])
39    assert_nil(ENV['TEST'])
40    ENV['test'] = 'foo'
41    assert_equal('foo', ENV['test'])
42    if IGNORE_CASE
43      assert_equal('foo', ENV['TEST'])
44    else
45      assert_nil(ENV['TEST'])
46    end
47    ENV['TEST'] = 'bar'
48    assert_equal('bar', ENV['TEST'])
49    assert_predicate(ENV['TEST'], :tainted?)
50    if IGNORE_CASE
51      assert_equal('bar', ENV['test'])
52    else
53      assert_equal('foo', ENV['test'])
54    end
55
56    assert_raise(TypeError) {
57      ENV[1]
58    }
59    assert_raise(TypeError) {
60      ENV[1] = 'foo'
61    }
62    assert_raise(TypeError) {
63      ENV['test'] = 0
64    }
65  end
66
67  def test_has_value
68    val = 'a'
69    val.succ! while ENV.has_value?(val) || ENV.has_value?(val.upcase)
70    ENV['test'] = val[0...-1]
71
72    assert_equal(false, ENV.has_value?(val))
73    assert_equal(false, ENV.has_value?(val.upcase))
74    ENV['test'] = val
75    assert_equal(true, ENV.has_value?(val))
76    assert_equal(false, ENV.has_value?(val.upcase))
77    ENV['test'] = val.upcase
78    assert_equal(false, ENV.has_value?(val))
79    assert_equal(true, ENV.has_value?(val.upcase))
80  end
81
82  def test_key
83    val = 'a'
84    val.succ! while ENV.has_value?(val) || ENV.has_value?(val.upcase)
85    ENV['test'] = val[0...-1]
86
87    assert_nil(ENV.key(val))
88    assert_nil(ENV.index(val))
89    assert_nil(ENV.key(val.upcase))
90    ENV['test'] = val
91    if IGNORE_CASE
92      assert_equal('TEST', ENV.key(val).upcase)
93    else
94      assert_equal('test', ENV.key(val))
95    end
96    assert_nil(ENV.key(val.upcase))
97    ENV['test'] = val.upcase
98    assert_nil(ENV.key(val))
99    if IGNORE_CASE
100      assert_equal('TEST', ENV.key(val.upcase).upcase)
101    else
102      assert_equal('test', ENV.key(val.upcase))
103    end
104  end
105
106  def test_delete
107    assert_invalid_env {|v| ENV.delete(v)}
108    assert_nil(ENV.delete("TEST"))
109    assert_nothing_raised { ENV.delete(PATH_ENV) }
110  end
111
112  def test_getenv
113    assert_invalid_env {|v| ENV[v]}
114    ENV[PATH_ENV] = ""
115    assert_equal("", ENV[PATH_ENV])
116    assert_predicate(ENV[PATH_ENV], :tainted?)
117    assert_nil(ENV[""])
118  end
119
120  def test_fetch
121    ENV["test"] = "foo"
122    assert_equal("foo", ENV.fetch("test"))
123    ENV.delete("test")
124    feature8649 = '[ruby-core:56062] [Feature #8649]'
125    e = assert_raise_with_message(KeyError, 'key not found: "test"', feature8649) do
126      ENV.fetch("test")
127    end
128    assert_same(ENV, e.receiver)
129    assert_equal("test", e.key)
130    assert_equal("foo", ENV.fetch("test", "foo"))
131    assert_equal("bar", ENV.fetch("test") { "bar" })
132    EnvUtil.suppress_warning do
133      assert_equal("bar", ENV.fetch("test", "foo") { "bar" })
134    end
135    assert_invalid_env {|v| ENV.fetch(v)}
136    assert_nothing_raised { ENV.fetch(PATH_ENV, "foo") }
137    ENV[PATH_ENV] = ""
138    assert_equal("", ENV.fetch(PATH_ENV))
139    assert_predicate(ENV.fetch(PATH_ENV), :tainted?)
140  end
141
142  def test_aset
143    assert_nothing_raised { ENV["test"] = nil }
144    assert_equal(nil, ENV["test"])
145    assert_invalid_env {|v| ENV[v] = "test"}
146    assert_invalid_env {|v| ENV["test"] = v}
147
148    begin
149      # setenv(3) allowed the name includes '=',
150      # but POSIX.1-2001 says it should fail with EINVAL.
151      # see also http://togetter.com/li/22380
152      ENV["foo=bar"] = "test"
153      assert_equal("test", ENV["foo=bar"])
154      assert_equal("test", ENV["foo"])
155    rescue Errno::EINVAL
156    end
157
158    ENV[PATH_ENV] = "/tmp/".taint
159    assert_equal("/tmp/", ENV[PATH_ENV])
160  end
161
162  def test_keys
163    a = ENV.keys
164    assert_kind_of(Array, a)
165    a.each {|k| assert_kind_of(String, k) }
166  end
167
168  def test_each_key
169    ENV.each_key {|k| assert_kind_of(String, k) }
170  end
171
172  def test_values
173    a = ENV.values
174    assert_kind_of(Array, a)
175    a.each {|k| assert_kind_of(String, k) }
176  end
177
178  def test_each_value
179    ENV.each_value {|k| assert_kind_of(String, k) }
180  end
181
182  def test_each_pair
183    ENV.each_pair do |k, v|
184      assert_kind_of(String, k)
185      assert_kind_of(String, v)
186    end
187  end
188
189  def test_reject_bang
190    h1 = {}
191    ENV.each_pair {|k, v| h1[k] = v }
192    ENV["test"] = "foo"
193    ENV.reject! {|k, v| IGNORE_CASE ? k.upcase == "TEST" : k == "test" }
194    h2 = {}
195    ENV.each_pair {|k, v| h2[k] = v }
196    assert_equal(h1, h2)
197
198    assert_nil(ENV.reject! {|k, v| IGNORE_CASE ? k.upcase == "TEST" : k == "test" })
199  end
200
201  def test_delete_if
202    h1 = {}
203    ENV.each_pair {|k, v| h1[k] = v }
204    ENV["test"] = "foo"
205    ENV.delete_if {|k, v| IGNORE_CASE ? k.upcase == "TEST" : k == "test" }
206    h2 = {}
207    ENV.each_pair {|k, v| h2[k] = v }
208    assert_equal(h1, h2)
209
210    assert_equal(ENV, ENV.delete_if {|k, v| IGNORE_CASE ? k.upcase == "TEST" : k == "test" })
211  end
212
213  def test_select_bang
214    h1 = {}
215    ENV.each_pair {|k, v| h1[k] = v }
216    ENV["test"] = "foo"
217    ENV.select! {|k, v| IGNORE_CASE ? k.upcase != "TEST" : k != "test" }
218    h2 = {}
219    ENV.each_pair {|k, v| h2[k] = v }
220    assert_equal(h1, h2)
221
222    assert_nil(ENV.select! {|k, v| IGNORE_CASE ? k.upcase != "TEST" : k != "test" })
223  end
224
225  def test_filter_bang
226    h1 = {}
227    ENV.each_pair {|k, v| h1[k] = v }
228    ENV["test"] = "foo"
229    ENV.filter! {|k, v| IGNORE_CASE ? k.upcase != "TEST" : k != "test" }
230    h2 = {}
231    ENV.each_pair {|k, v| h2[k] = v }
232    assert_equal(h1, h2)
233
234    assert_nil(ENV.filter! {|k, v| IGNORE_CASE ? k.upcase != "TEST" : k != "test" })
235  end
236
237  def test_keep_if
238    h1 = {}
239    ENV.each_pair {|k, v| h1[k] = v }
240    ENV["test"] = "foo"
241    ENV.keep_if {|k, v| IGNORE_CASE ? k.upcase != "TEST" : k != "test" }
242    h2 = {}
243    ENV.each_pair {|k, v| h2[k] = v }
244    assert_equal(h1, h2)
245
246    assert_equal(ENV, ENV.keep_if {|k, v| IGNORE_CASE ? k.upcase != "TEST" : k != "test" })
247  end
248
249  def test_values_at
250    ENV["test"] = "foo"
251    assert_equal(["foo", "foo"], ENV.values_at("test", "test"))
252  end
253
254  def test_select
255    ENV["test"] = "foo"
256    h = ENV.select {|k| IGNORE_CASE ? k.upcase == "TEST" : k == "test" }
257    assert_equal(1, h.size)
258    k = h.keys.first
259    v = h.values.first
260    if IGNORE_CASE
261      assert_equal("TEST", k.upcase)
262      assert_equal("FOO", v.upcase)
263    else
264      assert_equal("test", k)
265      assert_equal("foo", v)
266    end
267  end
268
269  def test_filter
270    ENV["test"] = "foo"
271    h = ENV.filter {|k| IGNORE_CASE ? k.upcase == "TEST" : k == "test" }
272    assert_equal(1, h.size)
273    k = h.keys.first
274    v = h.values.first
275    if IGNORE_CASE
276      assert_equal("TEST", k.upcase)
277      assert_equal("FOO", v.upcase)
278    else
279      assert_equal("test", k)
280      assert_equal("foo", v)
281    end
282  end
283
284  def test_slice
285    ENV.clear
286    ENV["foo"] = "bar"
287    ENV["baz"] = "qux"
288    ENV["bar"] = "rab"
289    assert_equal({}, ENV.slice())
290    assert_equal({}, ENV.slice(""))
291    assert_equal({}, ENV.slice("unknown"))
292    assert_equal({"foo"=>"bar", "baz"=>"qux"}, ENV.slice("foo", "baz"))
293  end
294
295  def test_clear
296    ENV.clear
297    assert_equal(0, ENV.size)
298  end
299
300  def test_to_s
301    assert_equal("ENV", ENV.to_s)
302  end
303
304  def test_inspect
305    ENV.clear
306    ENV["foo"] = "bar"
307    ENV["baz"] = "qux"
308    s = ENV.inspect
309    if IGNORE_CASE
310      s = s.upcase
311      assert(s == '{"FOO"=>"BAR", "BAZ"=>"QUX"}' || s == '{"BAZ"=>"QUX", "FOO"=>"BAR"}')
312    else
313      assert(s == '{"foo"=>"bar", "baz"=>"qux"}' || s == '{"baz"=>"qux", "foo"=>"bar"}')
314    end
315  end
316
317  def test_to_a
318    ENV.clear
319    ENV["foo"] = "bar"
320    ENV["baz"] = "qux"
321    a = ENV.to_a
322    assert_equal(2, a.size)
323    if IGNORE_CASE
324      a = a.map {|x| x.map {|y| y.upcase } }
325      assert(a == [%w(FOO BAR), %w(BAZ QUX)] || a == [%w(BAZ QUX), %w(FOO BAR)])
326    else
327      assert(a == [%w(foo bar), %w(baz qux)] || a == [%w(baz qux), %w(foo bar)])
328    end
329  end
330
331  def test_rehash
332    assert_nil(ENV.rehash)
333  end
334
335  def test_size
336    s = ENV.size
337    ENV["test"] = "foo"
338    assert_equal(s + 1, ENV.size)
339  end
340
341  def test_empty_p
342    ENV.clear
343    assert_predicate(ENV, :empty?)
344    ENV["test"] = "foo"
345    assert_not_predicate(ENV, :empty?)
346  end
347
348  def test_has_key
349    assert_not_send([ENV, :has_key?, "test"])
350    ENV["test"] = "foo"
351    assert_send([ENV, :has_key?, "test"])
352    assert_invalid_env {|v| ENV.has_key?(v)}
353  end
354
355  def test_assoc
356    assert_nil(ENV.assoc("test"))
357    ENV["test"] = "foo"
358    k, v = ENV.assoc("test")
359    if IGNORE_CASE
360      assert_equal("TEST", k.upcase)
361      assert_equal("FOO", v.upcase)
362    else
363      assert_equal("test", k)
364      assert_equal("foo", v)
365    end
366    assert_invalid_env {|var| ENV.assoc(var)}
367    assert_predicate(v, :tainted?)
368    assert_equal(Encoding.find("locale"), v.encoding)
369  end
370
371  def test_has_value2
372    ENV.clear
373    assert_not_send([ENV, :has_value?, "foo"])
374    ENV["test"] = "foo"
375    assert_send([ENV, :has_value?, "foo"])
376  end
377
378  def test_rassoc
379    ENV.clear
380    assert_nil(ENV.rassoc("foo"))
381    ENV["foo"] = "bar"
382    ENV["test"] = "foo"
383    ENV["baz"] = "qux"
384    k, v = ENV.rassoc("foo")
385    if IGNORE_CASE
386      assert_equal("TEST", k.upcase)
387      assert_equal("FOO", v.upcase)
388    else
389      assert_equal("test", k)
390      assert_equal("foo", v)
391    end
392  end
393
394  def test_to_hash
395    h = {}
396    ENV.each {|k, v| h[k] = v }
397    assert_equal(h, ENV.to_hash)
398  end
399
400  def test_to_h
401    assert_equal(ENV.to_hash, ENV.to_h)
402    assert_equal(ENV.map {|k, v| ["$#{k}", v.size]}.to_h,
403                 ENV.to_h {|k, v| ["$#{k}", v.size]})
404  end
405
406  def test_reject
407    h1 = {}
408    ENV.each_pair {|k, v| h1[k] = v }
409    ENV["test"] = "foo"
410    h2 = ENV.reject {|k, v| IGNORE_CASE ? k.upcase == "TEST" : k == "test" }
411    assert_equal(h1, h2)
412  end
413
414  def check(as, bs)
415    if IGNORE_CASE
416      as = as.map {|xs| xs.map {|x| x.upcase } }
417      bs = bs.map {|xs| xs.map {|x| x.upcase } }
418    end
419    assert_equal(as.sort, bs.sort)
420  end
421
422  def test_shift
423    ENV.clear
424    ENV["foo"] = "bar"
425    ENV["baz"] = "qux"
426    a = ENV.shift
427    b = ENV.shift
428    check([a, b], [%w(foo bar), %w(baz qux)])
429    assert_nil(ENV.shift)
430  end
431
432  def test_invert
433    ENV.clear
434    ENV["foo"] = "bar"
435    ENV["baz"] = "qux"
436    check(ENV.invert.to_a, [%w(bar foo), %w(qux baz)])
437  end
438
439  def test_replace
440    ENV["foo"] = "xxx"
441    ENV.replace({"foo"=>"bar", "baz"=>"qux"})
442    check(ENV.to_hash.to_a, [%w(foo bar), %w(baz qux)])
443    ENV.replace({"Foo"=>"Bar", "Baz"=>"Qux"})
444    check(ENV.to_hash.to_a, [%w(Foo Bar), %w(Baz Qux)])
445  end
446
447  def test_update
448    ENV.clear
449    ENV["foo"] = "bar"
450    ENV["baz"] = "qux"
451    ENV.update({"baz"=>"quux","a"=>"b"})
452    check(ENV.to_hash.to_a, [%w(foo bar), %w(baz quux), %w(a b)])
453
454    ENV.clear
455    ENV["foo"] = "bar"
456    ENV["baz"] = "qux"
457    ENV.update({"baz"=>"quux","a"=>"b"}) {|k, v1, v2| v1 ? k + "_" + v1 + "_" + v2 : v2 }
458    check(ENV.to_hash.to_a, [%w(foo bar), %w(baz baz_qux_quux), %w(a b)])
459  end
460
461  def test_huge_value
462    huge_value = "bar" * 40960
463    ENV["foo"] = "bar"
464    if /mswin/ =~ RUBY_PLATFORM
465      assert_raise(Errno::EINVAL) { ENV["foo"] = huge_value }
466      assert_equal("bar", ENV["foo"])
467    else
468      assert_nothing_raised { ENV["foo"] = huge_value }
469      assert_equal(huge_value, ENV["foo"])
470    end
471  end
472
473  if /mswin|mingw/ =~ RUBY_PLATFORM
474    def windows_version
475      @windows_version ||= %x[ver][/Version (\d+)/, 1].to_i
476    end
477
478    def test_win32_blocksize
479      keys = []
480      len = 32767 - ENV.to_a.flatten.inject(1) {|r,e| r + e.bytesize + 1}
481      val = "bar" * 1000
482      key = nil
483      while (len -= val.size + (key="foo#{len}").size + 2) > 0
484        keys << key
485        ENV[key] = val
486      end
487      if windows_version < 6
488        1.upto(12) {|i|
489          assert_raise(Errno::EINVAL) { ENV[key] = val }
490        }
491      else
492        1.upto(12) {|i|
493          assert_nothing_raised(Errno::EINVAL) { ENV[key] = val }
494        }
495      end
496    ensure
497      keys.each {|k| ENV.delete(k)}
498    end
499  end
500
501  def test_frozen
502    ENV[PATH_ENV] = "/"
503    ENV.each do |k, v|
504      assert_predicate(k, :frozen?)
505      assert_predicate(v, :frozen?)
506    end
507    ENV.each_key do |k|
508      assert_predicate(k, :frozen?)
509    end
510    ENV.each_value do |v|
511      assert_predicate(v, :frozen?)
512    end
513    ENV.each_key do |k|
514      assert_predicate(ENV[k], :frozen?, "[#{k.dump}]")
515      assert_predicate(ENV.fetch(k), :frozen?, "fetch(#{k.dump})")
516    end
517  end
518
519  def test_shared_substring
520    bug12475 = '[ruby-dev:49655] [Bug #12475]'
521    n = [*"0".."9"].join("")*3
522    e0 = ENV[n0 = "E#{n}"]
523    e1 = ENV[n1 = "E#{n}."]
524    ENV[n0] = nil
525    ENV[n1] = nil
526    ENV[n1.chop] = "T#{n}.".chop
527    ENV[n0], e0 = e0, ENV[n0]
528    ENV[n1], e1 = e1, ENV[n1]
529    assert_equal("T#{n}", e0, bug12475)
530    assert_nil(e1, bug12475)
531  end
532
533  if RUBY_PLATFORM =~ /bccwin|mswin|mingw/
534    def test_memory_leak_aset
535      bug9977 = '[ruby-dev:48323] [Bug #9977]'
536      assert_no_memory_leak([], <<-'end;', "5_000.times(&doit)", bug9977, limit: 2.0)
537        ENV.clear
538        k = 'FOO'
539        v = (ENV[k] = 'bar'*5000 rescue 'bar'*1500)
540        doit = proc {ENV[k] = v}
541        500.times(&doit)
542      end;
543    end
544
545    def test_memory_leak_select
546      bug9978 = '[ruby-dev:48325] [Bug #9978]'
547      assert_no_memory_leak([], <<-'end;', "5_000.times(&doit)", bug9978, limit: 2.0)
548        ENV.clear
549        k = 'FOO'
550        (ENV[k] = 'bar'*5000 rescue 'bar'*1500)
551        doit = proc {ENV.select {break}}
552        500.times(&doit)
553      end;
554    end
555
556    def test_memory_crash_select
557      assert_normal_exit(<<-'end;')
558        1000.times {ENV["FOO#{i}"] = 'bar'}
559        ENV.select {ENV.clear}
560      end;
561    end
562
563    def test_memory_leak_shift
564      bug9983 = '[ruby-dev:48332] [Bug #9983]'
565      assert_no_memory_leak([], <<-'end;', "5_000.times(&doit)", bug9983, limit: 2.0)
566        ENV.clear
567        k = 'FOO'
568        v = (ENV[k] = 'bar'*5000 rescue 'bar'*1500)
569        doit = proc {ENV[k] = v; ENV.shift}
570        500.times(&doit)
571      end;
572    end
573
574    if Encoding.find("locale") == Encoding::UTF_8
575      def test_utf8
576        text = "testing \u{e5 e1 e2 e4 e3 101 3042}"
577        test = ENV["test"]
578        ENV["test"] = text
579        assert_equal text, ENV["test"]
580      ensure
581        ENV["test"] = test
582      end
583    end
584  end
585end
586