1require 'test/unit'
2require 'tmpdir'
3
4begin
5  require 'dbm'
6rescue LoadError
7end
8
9if defined? DBM
10  require 'tmpdir'
11  require 'fileutils'
12
13  class TestDBM_RDONLY < Test::Unit::TestCase
14    def TestDBM_RDONLY.uname_s
15      require 'rbconfig'
16      case RbConfig::CONFIG['target_os']
17      when 'cygwin'
18        require 'Win32API'
19        uname = Win32API.new('cygwin1', 'uname', 'P', 'I')
20        utsname = ' ' * 100
21        raise 'cannot get system name' if uname.call(utsname) == -1
22
23        utsname.unpack('A20' * 5)[0]
24      else
25        RbConfig::CONFIG['target_os']
26      end
27    end
28    SYSTEM = uname_s
29
30    def setup
31      @tmpdir = Dir.mktmpdir("tmptest_dbm")
32      @prefix = "tmptest_dbm_#{$$}"
33      @path = "#{@tmpdir}/#{@prefix}_"
34
35      # prepare to make readonly DBM file
36      DBM.open("#{@tmpdir}/#{@prefix}_rdonly") {|dbm|
37        dbm['foo'] = 'FOO'
38      }
39
40      File.chmod(0400, *Dir.glob("#{@tmpdir}/#{@prefix}_rdonly.*"))
41
42      assert_instance_of(DBM, @dbm_rdonly = DBM.new("#{@tmpdir}/#{@prefix}_rdonly", nil))
43    end
44    def teardown
45      assert_nil(@dbm_rdonly.close)
46      ObjectSpace.each_object(DBM) do |obj|
47        obj.close unless obj.closed?
48      end
49      FileUtils.remove_entry_secure @tmpdir
50    end
51
52    def test_delete_rdonly
53      if /^CYGWIN_9/ !~ SYSTEM
54        assert_raise(DBMError) {
55          @dbm_rdonly.delete("foo")
56        }
57
58        assert_nil(@dbm_rdonly.delete("bar"))
59      end
60    end
61  end
62
63  class TestDBM < Test::Unit::TestCase
64    def setup
65      @tmpdir = Dir.mktmpdir("tmptest_dbm")
66      @prefix = "tmptest_dbm_#{$$}"
67      @path = "#{@tmpdir}/#{@prefix}_"
68      assert_instance_of(DBM, @dbm = DBM.new(@path))
69    end
70    def teardown
71      assert_nil(@dbm.close) unless @dbm.closed?
72      ObjectSpace.each_object(DBM) do |obj|
73        obj.close unless obj.closed?
74      end
75      FileUtils.remove_entry_secure @tmpdir
76    end
77
78    def check_size(expect, dbm=@dbm)
79      assert_equal(expect, dbm.size)
80      n = 0
81      dbm.each { n+=1 }
82      assert_equal(expect, n)
83      if expect == 0
84        assert_equal(true, dbm.empty?)
85      else
86        assert_equal(false, dbm.empty?)
87      end
88    end
89
90    def have_fork?
91      begin
92        fork{}
93        true
94      rescue NotImplementedError
95        false
96      end
97    end
98
99    def test_dbmfile_suffix
100      @dbm.close
101      prefix = File.basename(@path)
102      suffixes = Dir.entries(@tmpdir).grep(/\A#{Regexp.escape prefix}/) { $' }.sort
103      pagname = "#{@path}.pag"
104      dirname = "#{@path}.dir"
105      dbname = "#{@path}.db"
106      case DBM::VERSION
107      when /\bNDBM\b/
108        assert_equal(%w[.dir .pag], suffixes)
109        assert(File.zero?(pagname))
110        assert(File.zero?(dirname))
111      when /\bGDBM\b/
112        assert_equal(%w[.dir .pag], suffixes)
113        assert(!File.zero?(pagname))
114        assert(!File.zero?(dirname))
115        pag = File.binread(pagname, 16)
116        pag_magics = [
117          0x13579ace, # GDBM_OMAGIC
118          0x13579acd, # GDBM_MAGIC32
119          0x13579acf, # GDBM_MAGIC64
120        ]
121        assert_operator(pag_magics, :include?,
122                        pag.unpack("i")[0]) # native endian, native int.
123        if !File.identical?(pagname, dirname)
124          dir = File.binread(dirname, 16)
125          assert_equal("GDBM", dir[0, 4])
126        end
127      when /\bBerkeley DB\b/
128        assert_equal(%w[.db], suffixes)
129        assert(!File.zero?(dbname))
130        db = File.binread(dbname, 16)
131        assert(db[0,4].unpack("N") == [0x00061561] || # Berkeley DB 1
132               db[12,4].unpack("L") == [0x00061561]) # Berkeley DBM 2 or later.
133      when /\bQDBM\b/
134        assert_equal(%w[.dir .pag], suffixes)
135        assert(!File.zero?(pagname))
136        assert(!File.zero?(dirname))
137        dir = File.binread(dirname, 16)
138        assert_equal("[depot]\0\v", dir[0, 9])
139        pag = File.binread(pagname, 16)
140        if [1].pack("s") == "\x00\x01" # big endian
141          assert_equal("[DEPOT]\n\f", pag[0, 9])
142        else # little endian
143          assert_equal("[depot]\n\f", pag[0, 9])
144        end
145      end
146      if suffixes == %w[.db]
147        assert_match(/\bBerkeley DB\b/, DBM::VERSION)
148      end
149    end
150
151    def test_s_new_has_no_block
152      # DBM.new ignore the block
153      foo = true
154      assert_instance_of(DBM, dbm = DBM.new("#{@tmpdir}/#{@prefix}") { foo = false })
155      assert_equal(foo, true)
156      assert_nil(dbm.close)
157    end
158
159    def test_s_open_no_create
160      skip "dbm_open() is broken on libgdbm 1.8.0 or prior (#{DBM::VERSION})" if /GDBM version 1\.(?:[0-7]\b|8\.0)/ =~ DBM::VERSION
161      assert_nil(dbm = DBM.open("#{@tmpdir}/#{@prefix}", nil))
162    ensure
163      dbm.close if dbm
164    end
165
166    def test_s_open_with_block
167      assert_equal(DBM.open("#{@tmpdir}/#{@prefix}") { :foo }, :foo)
168    end
169
170    def test_close
171      assert_instance_of(DBM, dbm = DBM.open("#{@tmpdir}/#{@prefix}"))
172      assert_nil(dbm.close)
173
174      # closed DBM file
175      assert_raise(DBMError) { dbm.close }
176    end
177
178    def test_aref
179      assert_equal('bar', @dbm['foo'] = 'bar')
180      assert_equal('bar', @dbm['foo'])
181
182      assert_nil(@dbm['bar'])
183    end
184
185    def test_fetch
186      assert_equal('bar', @dbm['foo']='bar')
187      assert_equal('bar', @dbm.fetch('foo'))
188
189      # key not found
190      assert_raise(IndexError) {
191        @dbm.fetch('bar')
192      }
193
194      # test for `ifnone' arg
195      assert_equal('baz', @dbm.fetch('bar', 'baz'))
196
197      # test for `ifnone' block
198      assert_equal('foobar', @dbm.fetch('bar') {|key| 'foo' + key })
199    end
200
201    def test_aset
202      num = 0
203      2.times {|i|
204        assert_equal('foo', @dbm['foo'] = 'foo')
205        assert_equal('foo', @dbm['foo'])
206        assert_equal('bar', @dbm['foo'] = 'bar')
207        assert_equal('bar', @dbm['foo'])
208
209        num += 1 if i == 0
210        assert_equal(num, @dbm.size)
211
212        # assign nil
213        assert_equal('', @dbm['bar'] = '')
214        assert_equal('', @dbm['bar'])
215
216        num += 1 if i == 0
217        assert_equal(num, @dbm.size)
218
219        # empty string
220        assert_equal('', @dbm[''] = '')
221        assert_equal('', @dbm[''])
222
223        num += 1 if i == 0
224        assert_equal(num, @dbm.size)
225
226        # Fixnum
227        assert_equal('200', @dbm['100'] = '200')
228        assert_equal('200', @dbm['100'])
229
230        num += 1 if i == 0
231        assert_equal(num, @dbm.size)
232
233        # Big key and value
234        assert_equal('y' * 100, @dbm['x' * 100] = 'y' * 100)
235        assert_equal('y' * 100, @dbm['x' * 100])
236
237        num += 1 if i == 0
238        assert_equal(num, @dbm.size)
239      }
240    end
241
242    def test_key
243      assert_equal('bar', @dbm['foo'] = 'bar')
244      assert_equal('foo', @dbm.key('bar'))
245      assert_nil(@dbm['bar'])
246    end
247
248    def test_values_at
249      keys = %w(foo bar baz)
250      values = %w(FOO BAR BAZ)
251      @dbm[keys[0]], @dbm[keys[1]], @dbm[keys[2]] = values
252      assert_equal(values.reverse, @dbm.values_at(*keys.reverse))
253    end
254
255    def test_select_with_block
256      keys = %w(foo bar baz)
257      values = %w(FOO BAR BAZ)
258      @dbm[keys[0]], @dbm[keys[1]], @dbm[keys[2]] = values
259      ret = @dbm.select {|k,v|
260        assert_equal(k.upcase, v)
261        k != "bar"
262      }
263      assert_equal([['baz', 'BAZ'], ['foo', 'FOO']],
264                    ret.sort)
265    end
266
267    def test_length
268      num = 10
269      assert_equal(0, @dbm.size)
270      num.times {|i|
271        i = i.to_s
272        @dbm[i] = i
273      }
274      assert_equal(num, @dbm.size)
275
276      @dbm.shift
277
278      assert_equal(num - 1, @dbm.size)
279    end
280
281    def test_empty?
282      assert_equal(true, @dbm.empty?)
283      @dbm['foo'] = 'FOO'
284      assert_equal(false, @dbm.empty?)
285    end
286
287    def test_each_pair
288      n = 0
289      @dbm.each_pair { n += 1 }
290      assert_equal(0, n)
291
292      keys = %w(foo bar baz)
293      values = %w(FOO BAR BAZ)
294
295      @dbm[keys[0]], @dbm[keys[1]], @dbm[keys[2]] = values
296
297      n = 0
298      ret = @dbm.each_pair {|key, val|
299        assert_not_nil(i = keys.index(key))
300        assert_equal(val, values[i])
301
302        n += 1
303      }
304      assert_equal(keys.size, n)
305      assert_equal(@dbm, ret)
306    end
307
308    def test_each_value
309      n = 0
310      @dbm.each_value { n += 1 }
311      assert_equal(0, n)
312
313      keys = %w(foo bar baz)
314      values = %w(FOO BAR BAZ)
315
316      @dbm[keys[0]], @dbm[keys[1]], @dbm[keys[2]] = values
317
318      n = 0
319      ret = @dbm.each_value {|val|
320        assert_not_nil(key = @dbm.key(val))
321        assert_not_nil(i = keys.index(key))
322        assert_equal(val, values[i])
323
324        n += 1
325      }
326      assert_equal(keys.size, n)
327      assert_equal(@dbm, ret)
328    end
329
330    def test_each_key
331      n = 0
332      @dbm.each_key { n += 1 }
333      assert_equal(0, n)
334
335      keys = %w(foo bar baz)
336      values = %w(FOO BAR BAZ)
337
338      @dbm[keys[0]], @dbm[keys[1]], @dbm[keys[2]] = values
339
340      n = 0
341      ret = @dbm.each_key {|key|
342        assert_not_nil(i = keys.index(key))
343        assert_equal(@dbm[key], values[i])
344
345        n += 1
346      }
347      assert_equal(keys.size, n)
348      assert_equal(@dbm, ret)
349    end
350
351    def test_keys
352      assert_equal([], @dbm.keys)
353
354      keys = %w(foo bar baz)
355      values = %w(FOO BAR BAZ)
356
357      @dbm[keys[0]], @dbm[keys[1]], @dbm[keys[2]] = values
358
359      assert_equal(keys.sort, @dbm.keys.sort)
360      assert_equal(values.sort, @dbm.values.sort)
361    end
362
363    def test_values
364      test_keys
365    end
366
367    def test_shift
368      assert_nil(@dbm.shift)
369      assert_equal(0, @dbm.size)
370
371      keys = %w(foo bar baz)
372      values = %w(FOO BAR BAZ)
373
374      @dbm[keys[0]], @dbm[keys[1]], @dbm[keys[2]] = values
375
376      ret_keys = []
377      ret_values = []
378      while ret = @dbm.shift
379        ret_keys.push ret[0]
380        ret_values.push ret[1]
381
382        assert_equal(keys.size - ret_keys.size, @dbm.size)
383      end
384
385      assert_equal(keys.sort, ret_keys.sort)
386      assert_equal(values.sort, ret_values.sort)
387    end
388
389    def test_delete
390      keys = %w(foo bar baz)
391      values = %w(FOO BAR BAZ)
392      key = keys[1]
393
394      assert_nil(@dbm.delete(key))
395      assert_equal(0, @dbm.size)
396
397      @dbm[keys[0]], @dbm[keys[1]], @dbm[keys[2]] = values
398
399      assert_equal('BAR', @dbm.delete(key))
400      assert_nil(@dbm[key])
401      assert_equal(2, @dbm.size)
402
403      assert_nil(@dbm.delete(key))
404    end
405
406    def test_delete_with_block
407      key = 'no called block'
408      @dbm[key] = 'foo'
409      assert_equal('foo', @dbm.delete(key) {|k| k.replace 'called block'; :blockval})
410      assert_equal(0, @dbm.size)
411
412      key = 'no called block'
413      assert_equal(:blockval, @dbm.delete(key) {|k| k.replace 'called block'; :blockval})
414      assert_equal(0, @dbm.size)
415    end
416
417    def test_delete_if
418      v = "0"
419      100.times {@dbm[v] = v; v = v.next}
420
421      ret = @dbm.delete_if {|key, val| key.to_i < 50}
422      assert_equal(@dbm, ret)
423      check_size(50, @dbm)
424
425      ret = @dbm.delete_if {|key, val| key.to_i >= 50}
426      assert_equal(@dbm, ret)
427      check_size(0, @dbm)
428
429      # break
430      v = "0"
431      100.times {@dbm[v] = v; v = v.next}
432      check_size(100, @dbm)
433      n = 0;
434      @dbm.delete_if {|key, val|
435        break if n > 50
436        n+=1
437        true
438      }
439      assert_equal(51, n)
440      check_size(49, @dbm)
441
442      @dbm.clear
443
444      # raise
445      v = "0"
446      100.times {@dbm[v] = v; v = v.next}
447      check_size(100, @dbm)
448      n = 0;
449      begin
450        @dbm.delete_if {|key, val|
451          raise "runtime error" if n > 50
452          n+=1
453          true
454        }
455      rescue
456      end
457      assert_equal(51, n)
458      check_size(49, @dbm)
459    end
460
461    def test_reject
462      v = "0"
463      100.times {@dbm[v] = v; v = v.next}
464
465      hash = @dbm.reject {|key, val| key.to_i < 50}
466      assert_instance_of(Hash, hash)
467      assert_equal(100, @dbm.size)
468
469      assert_equal(50, hash.size)
470      hash.each_pair {|key,val|
471        assert_equal(false, key.to_i < 50)
472        assert_equal(key, val)
473      }
474
475      hash = @dbm.reject {|key, val| key.to_i < 100}
476      assert_instance_of(Hash, hash)
477      assert_equal(true, hash.empty?)
478    end
479
480    def test_clear
481      v = "1"
482      100.times {v = v.next; @dbm[v] = v}
483
484      assert_equal(@dbm, @dbm.clear)
485
486      # validate DBM#size
487      i = 0
488      @dbm.each { i += 1 }
489      assert_equal(@dbm.size, i)
490      assert_equal(0, i)
491    end
492
493    def test_invert
494      v = "0"
495      100.times {@dbm[v] = v; v = v.next}
496
497      hash = @dbm.invert
498      assert_instance_of(Hash, hash)
499      assert_equal(100, hash.size)
500      hash.each_pair {|key, val|
501        assert_equal(key.to_i, val.to_i)
502      }
503    end
504
505    def test_update
506      hash = {}
507      v = "0"
508      100.times {v = v.next; hash[v] = v}
509
510      @dbm["101"] = "101"
511      @dbm.update hash
512      assert_equal(101, @dbm.size)
513      @dbm.each_pair {|key, val|
514        assert_equal(key.to_i, val.to_i)
515      }
516    end
517
518    def test_replace
519      hash = {}
520      v = "0"
521      100.times {v = v.next; hash[v] = v}
522
523      @dbm["101"] = "101"
524      @dbm.replace hash
525      assert_equal(100, @dbm.size)
526      @dbm.each_pair {|key, val|
527        assert_equal(key.to_i, val.to_i)
528      }
529    end
530
531    def test_haskey?
532      assert_equal('bar', @dbm['foo']='bar')
533      assert_equal(true,  @dbm.has_key?('foo'))
534      assert_equal(false, @dbm.has_key?('bar'))
535    end
536
537    def test_has_value?
538      assert_equal('bar', @dbm['foo']='bar')
539      assert_equal(true,  @dbm.has_value?('bar'))
540      assert_equal(false, @dbm.has_value?('foo'))
541    end
542
543    def test_to_a
544      v = "0"
545      100.times {v = v.next; @dbm[v] = v}
546
547      ary = @dbm.to_a
548      assert_instance_of(Array, ary)
549      assert_equal(100, ary.size)
550      ary.each {|key,val|
551        assert_equal(key.to_i, val.to_i)
552      }
553    end
554
555    def test_to_hash
556      v = "0"
557      100.times {v = v.next; @dbm[v] = v}
558
559      hash = @dbm.to_hash
560      assert_instance_of(Hash, hash)
561      assert_equal(100, hash.size)
562      hash.each {|key,val|
563        assert_equal(key.to_i, val.to_i)
564      }
565    end
566  end
567
568  class TestDBM2 < Test::Unit::TestCase
569    def setup
570      @tmproot = Dir.mktmpdir('ruby-dbm')
571    end
572
573    def teardown
574      FileUtils.remove_entry_secure @tmproot if File.directory?(@tmproot)
575    end
576
577    def test_version
578      assert_instance_of(String, DBM::VERSION)
579    end
580
581    def test_reader_open_notexist
582      assert_raise(Errno::ENOENT) {
583        DBM.open("#{@tmproot}/a", 0666, DBM::READER)
584      }
585    end
586
587    def test_writer_open_notexist
588      skip "dbm_open() is broken on libgdbm 1.8.0 or prior (#{DBM::VERSION})" if /GDBM version 1\.(?:[0-7]\b|8\.0)/ =~ DBM::VERSION
589      assert_raise(Errno::ENOENT) {
590        DBM.open("#{@tmproot}/a", 0666, DBM::WRITER)
591      }
592    end
593
594    def test_wrcreat_open_notexist
595      v = DBM.open("#{@tmproot}/a", 0666, DBM::WRCREAT)
596      assert_instance_of(DBM, v)
597      v.close
598    end
599
600    def test_newdb_open_notexist
601      v = DBM.open("#{@tmproot}/a", 0666, DBM::NEWDB)
602      assert_instance_of(DBM, v)
603      v.close
604    end
605
606    def test_reader_open
607      DBM.open("#{@tmproot}/a") {} # create a db.
608      v = DBM.open("#{@tmproot}/a", nil, DBM::READER) {|d|
609        # Errno::EPERM is raised on Solaris which use ndbm.
610        # DBMError is raised on Debian which use gdbm.
611        assert_raise(Errno::EPERM, DBMError) { d["k"] = "v" }
612        true
613      }
614      assert(v)
615    end
616
617    def test_newdb_open
618      DBM.open("#{@tmproot}/a") {|dbm|
619        dbm["k"] = "v"
620      }
621      v = DBM.open("#{@tmproot}/a", nil, DBM::NEWDB) {|d|
622        assert_equal(0, d.length)
623        assert_nil(d["k"])
624        true
625      }
626      assert(v)
627    end
628
629    def test_freeze
630      DBM.open("#{@tmproot}/a") {|d|
631        d.freeze
632        assert_raise(RuntimeError) { d["k"] = "v" }
633      }
634    end
635  end
636end
637