dbmlite3 1.0.a1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,960 @@
1
+
2
+ # Tests for *some* of util.rb; this is nowhere near complete.
3
+
4
+ gem "sqlite3", "~> 1.4"
5
+
6
+ require_relative '../lib/dbmlite3.rb'
7
+ require 'fileutils'
8
+ require 'set'
9
+
10
+ module Tmp
11
+ @root = File.join( File.dirname(__FILE__), "tmpdata")
12
+ @count = 0
13
+
14
+ def self.file
15
+ FileUtils.mkdir(@root) unless File.directory?(@root)
16
+
17
+ file = "testfile_#{@count}_#{$$}.sqlite3"
18
+ @count += 1
19
+
20
+ return File.join(@root, file)
21
+ end
22
+
23
+ def self.cleanup
24
+ return unless File.directory?(@root)
25
+ FileUtils.rm_rf(@root)
26
+ end
27
+ end
28
+
29
+ # def enum_finish(enum)
30
+ # result = []
31
+ # result.push enum.next while true
32
+ # rescue StopIteration => e
33
+ # return result
34
+ # end
35
+
36
+ AStruct = Struct.new(:a, :b, :c)
37
+ Serializations = Set.new
38
+
39
+ [:yaml, :marshal] .each do |enc_arg|
40
+ enc = enc_arg
41
+ newdb = proc{ |path, table|
42
+ Serializations << enc
43
+ Lite3::DBM.new(path, table, enc)
44
+ }
45
+
46
+
47
+
48
+ describe Lite3::DBM do
49
+ after(:all) do
50
+ Tmp.cleanup
51
+ end
52
+
53
+ it "creates files and can reopen them afterward" do
54
+ path = Tmp.file
55
+ expect( File.exist?(path) ) .to be false
56
+
57
+ # Create it twice to ensure this is safe
58
+ db = newdb.call(path, "second")
59
+ db.close
60
+
61
+ db2 = newdb.call(path, "second")
62
+ db2.close
63
+
64
+ expect( File.exist?(path) ) .to be true
65
+ end
66
+
67
+ it "allows closing of DB files" do
68
+ path = Tmp.file
69
+ expect( File.exist?(path) ) .to be false
70
+
71
+ # Create it twice to ensure this is safe
72
+ db = newdb.call(path, "first")
73
+ db2 = newdb.call(path, "second")
74
+
75
+ expect( db.closed? ) .to be false
76
+ expect( db2.closed? ) .to be false
77
+
78
+ db.close
79
+ expect( db.closed? ) .to be true
80
+ expect( db2.closed? ) .to be false
81
+
82
+ db2.close
83
+ expect( db2.closed? ) .to be true
84
+ end
85
+
86
+ it "allows insertion, reading, and replacement of values" do
87
+ path = Tmp.file
88
+ expect( File.exist?(path) ) .to be false
89
+
90
+ # Create it twice to ensure this is safe
91
+ db = newdb.call(path, "first")
92
+
93
+ db["foo"] = 42
94
+ expect( db['foo'] ) .to eq 42
95
+
96
+ db["bar"] = [1,2,3]
97
+ expect( db['bar'] ) .to eq [1,2,3]
98
+
99
+ db["bar"] = "Hi, there!"
100
+ expect( db['bar'] ) .to eq "Hi, there!"
101
+
102
+ db.store("bar", "nope")
103
+ expect( db['bar'] ) .to eq "nope"
104
+
105
+ db.close
106
+ end
107
+
108
+ it "retrieves the list of keys and values" do
109
+ path = Tmp.file
110
+ db = newdb.call(path, "floop")
111
+
112
+ expect( db.keys ) .to eq []
113
+ expect( db.values ) .to eq []
114
+
115
+ db["foo"] = 42
116
+ db["bar"] = 99
117
+ db["quux"] = 123
118
+
119
+ expect( db.keys ) .to eq %w{foo bar quux}
120
+ expect( db.values ) .to eq [42, 99, 123]
121
+
122
+ db.close
123
+ end
124
+
125
+ it "implements has_key?" do
126
+ path = Tmp.file
127
+ db = newdb.call(path, "floop")
128
+
129
+ expect( db.has_key? :foo ) .to be false
130
+
131
+ db["foo"] = 42
132
+ db["bar"] = 99
133
+ db["quux"] = 123
134
+
135
+ expect( db.has_key? :foo ) .to be true
136
+ expect( db.include? 'foo' ) .to be true
137
+ expect( db.has_key? 'bar' ) .to be true
138
+ expect( db.has_key? :quux ) .to be true
139
+
140
+ expect( db.key? :quux ) .to be true
141
+ expect( db.include? :quux ) .to be true
142
+ expect( db.member? :quux ) .to be true
143
+
144
+ expect( db.has_key? '' ) .to be false
145
+ expect( db.has_key? 'norlllp' ) .to be false
146
+ expect( db.include? 'norlllp' ) .to be false
147
+
148
+ db.close
149
+ end
150
+
151
+ it "fails if the key isn't a string or symbol" do
152
+ path = Tmp.file
153
+ db = newdb.call(path, "floop")
154
+
155
+ db[:foo] = 42
156
+ expect( db['foo'] ) .to eq 42
157
+
158
+ expect{ db[ [1,2,3] ] = 999 } .to raise_error TypeError
159
+
160
+ db.close
161
+ end
162
+
163
+ it "implements .clear (deletes all items)" do
164
+ path = Tmp.file
165
+ db = newdb.call(path, "floop")
166
+
167
+ db["foo"] = 42
168
+ db["bar"] = 99
169
+ db["quux"] = 123
170
+
171
+ expect( db.keys.sort ) .to eq %w{bar foo quux}
172
+ expect( db['quux'] ) .to be 123
173
+
174
+ db.clear
175
+
176
+ expect( db.keys.sort ) .to eq []
177
+ expect( db['foo'] ) .to eq nil
178
+
179
+ db.close
180
+ end
181
+
182
+
183
+ it "preserves insertion order" do
184
+ db = newdb.call(Tmp.file, "floop")
185
+
186
+ db["foo"] = 42
187
+ db["bar"] = 99
188
+ db["quux"] = 123
189
+
190
+ expect( db.keys ) .to eq %w{foo bar quux}
191
+
192
+ db.close
193
+ end
194
+
195
+ it "implements each_*" do
196
+ db = newdb.call(Tmp.file, "floop")
197
+
198
+ count = 0
199
+ db.each {|k,v| count += 1}
200
+ db.each_pair {|k,v| count += 1}
201
+
202
+
203
+ db["foo"] = 42
204
+ db["bar"] = 99
205
+ db["quux"] = 123
206
+
207
+ pairs = []
208
+ db.each_pair{|k,v| pairs.push [k,v]}
209
+ expect( pairs ) .to eq [ ["foo", 42], ["bar", 99], ["quux", 123] ]
210
+
211
+ keys = []
212
+ db.each_key{|k| keys.push k }
213
+ expect( keys ) .to eq [ "foo", "bar", "quux" ]
214
+
215
+ values = []
216
+ db.each_value{|k| values.push k }
217
+ expect( values ) .to eq [ 42, 99, 123 ]
218
+
219
+ db.close
220
+ end
221
+
222
+ it "deletes items from the table" do
223
+ db = newdb.call(Tmp.file, "floop")
224
+
225
+ db["foo"] = 42
226
+ db["bar"] = 99
227
+ db["quux"] = 123
228
+ expect( db.keys ) .to eq %w{foo bar quux}
229
+
230
+ db.delete('bar')
231
+ expect( db.keys ) .to eq %w{foo quux}
232
+ expect( db.has_key? 'bar' ) .to be false
233
+
234
+ db.close
235
+ end
236
+
237
+ it "deletes items from the table if they match a block" do
238
+ db = newdb.call(Tmp.file, "floop")
239
+
240
+ db["foo"] = 42
241
+ db["bar"] = 99
242
+ db["quux"] = 123
243
+ expect( db.keys ) .to eq %w{foo bar quux}
244
+
245
+ db.delete_if {|k,v| k == "bar"}
246
+ expect( db.keys ) .to eq %w{foo quux}
247
+ expect( db.has_key? 'bar' ) .to be false
248
+
249
+ db.delete_if {|k,v| false }
250
+ expect( db.keys ) .to eq %w{foo quux}
251
+ expect( db.has_key? 'bar' ) .to be false
252
+
253
+ db.delete_if {|k,v| v == 123 }
254
+ expect( db.keys ) .to eq %w{foo}
255
+ expect( db.has_key? 'quux' ) .to be false
256
+
257
+ db.close
258
+ end
259
+
260
+ it "returns size, length and tests for emptyness" do
261
+ db = newdb.call(Tmp.file, "floop")
262
+
263
+ expect( db.empty? ) .to be true
264
+
265
+ db["foo"] = 42
266
+ expect( db.empty? ) .to be false
267
+ expect( db.size ) .to be 1
268
+
269
+ db["bar"] = 99
270
+ expect( db.size ) .to be 2
271
+
272
+ db["quux"] = 123
273
+ expect( db.size ) .to be 3
274
+
275
+ db.delete("bar")
276
+ expect( db.size ) .to be 2
277
+ expect( db.length ) .to be 2
278
+
279
+ db.close
280
+ end
281
+
282
+ it "implements 'fetch'" do
283
+ db = newdb.call(Tmp.file, "floop")
284
+ db.store("foo", 42)
285
+
286
+ expect( db.fetch("foo", 999) { |k| 69} ) .to eq 42
287
+ expect( db.fetch("foo", 999) ) .to eq 42
288
+ expect( db.fetch("foo") ) .to eq 42
289
+
290
+ expect( db.fetch("foox", 999) { |k| 69} ) .to eq 69
291
+ expect( db.fetch("foox", 999) ) .to eq 999
292
+
293
+ expect{ db.fetch("foox") } .to raise_error IndexError
294
+
295
+ expect{ db.fetch([1,2,3]) } .to raise_error TypeError
296
+
297
+ db.close
298
+ end
299
+
300
+ it "implements to_a and to_hash" do
301
+ db = newdb.call(Tmp.file, "floop")
302
+
303
+ expect( db.to_a ) .to eq []
304
+ expect( db.to_hash ) .to eq( {} )
305
+
306
+ db["foo"] = 42
307
+ db["bar"] = 99
308
+ db["quux"] = 123
309
+
310
+ expect( db.to_a ) .to eq [ ['foo', 42], ['bar', 99], ['quux', 123] ]
311
+ expect( db.to_hash )
312
+ .to eq [ ['foo', 42], ['bar', 99], ['quux', 123] ].to_h
313
+
314
+ db.close
315
+ end
316
+
317
+ it "stores complex data structures" do
318
+ db = newdb.call(Tmp.file, "floop")
319
+
320
+ thingy = AStruct.new([1, 2, "bar"], :b, {this_field: 42})
321
+ db["thingy"] = thingy
322
+ expect( db["thingy"] ) .to eq thingy
323
+
324
+ db.close
325
+ end
326
+
327
+ it "implements 'update'." do
328
+ db = newdb.call(Tmp.file, "floop")
329
+ expect( db.empty? ) .to be true
330
+
331
+ vv = {
332
+ foo: 123,
333
+ bar: "this is some text",
334
+ quux: [1,2,3],
335
+ }
336
+
337
+ db.update(vv)
338
+
339
+ vv.each{|k,v| expect( db[k] ) .to eq v }
340
+
341
+ db.close
342
+ end
343
+
344
+ it "implements nestable 'transaction'" do
345
+ db = newdb.call(Tmp.file, "floop")
346
+
347
+ db.transaction {|db1|
348
+ expect( db1 ) .to be db
349
+
350
+ expect( db.to_a ) .to eq []
351
+ expect( db.to_hash ) .to eq( {} )
352
+
353
+ db.transaction {|db2|
354
+ db2["foo"] = 42
355
+ db2["bar"] = 99
356
+ }
357
+
358
+ # Test sequences of nested transactions
359
+ db.transaction {|db2|
360
+ db2["quux"] = 123
361
+ }
362
+ }
363
+
364
+ expect( db.to_a ) .to eq [ ['foo', 42], ['bar', 99], ['quux', 123] ]
365
+
366
+ # Test a sequence of toplevel transactions
367
+ db.transaction {
368
+ db["bobo"] = [1,2,3]
369
+ }
370
+
371
+ expect( db["bobo"] ) .to eq [1,2,3]
372
+
373
+ db.close
374
+ end
375
+
376
+ it "rolls back transactions on exception" do
377
+ db = newdb.call(Tmp.file, "floop")
378
+
379
+ expect do
380
+ db.transaction {
381
+ db["foo"] = 42
382
+ db["bar"] = 99
383
+ db["quux"] = 123
384
+
385
+ raise "nope"
386
+ }
387
+ end .to raise_error RuntimeError
388
+
389
+ expect( db.empty? ) .to be true
390
+
391
+ db.close
392
+ end
393
+
394
+ it "provides the rest of DBM's interface as convenience methods." do
395
+ db = newdb.call(Tmp.file, "blopp")
396
+ vv = {
397
+ "foo" => 123,
398
+ "bar" => "this is some text",
399
+ "quux" => [1,2,3],
400
+ }
401
+
402
+ db.update(vv)
403
+
404
+ expect( db.values_at('foo', 'nope', 'quux') )
405
+ .to eq vv.values_at('foo', 'nope', 'quux')
406
+
407
+ expect( db.value? 123 ) .to be true
408
+ expect( db.value? "nice" ) .to be false
409
+
410
+ expect( db.invert ) .to eq vv.invert
411
+
412
+ expect( db.has_value? [1,2,3] ) .to be true
413
+ expect( db.has_value? "nope") .to be false
414
+
415
+ expect( db.shift ) .to eq vv.shift
416
+ expect( db.to_hash ) .to eq vv
417
+
418
+ db.close
419
+ end
420
+
421
+ it "has each* methods return an enumerator if no block is given" do
422
+ db = newdb.call(Tmp.file, "blopp")
423
+ vv = {
424
+ "foo" => 123,
425
+ "bar" => "this is some text",
426
+ "quux" => [1,2,3],
427
+ }
428
+ db.update(vv)
429
+
430
+ e1 = db.each
431
+ expect( e1.class ) .to be Enumerator
432
+ expect( e1.next ) .to eq ["foo", 123]
433
+ expect{ e1.next; e1.next; e1.next } .to raise_error StopIteration
434
+
435
+ e2 = db.each_key
436
+ expect( e2.class ) .to be Enumerator
437
+ expect( e2.next ) .to eq "foo"
438
+ expect( e2.next ) .to eq "bar"
439
+ expect{ e2.next; e2.next; e2.next } .to raise_error StopIteration
440
+
441
+ e3 = db.each_value
442
+ expect( e3.class ) .to be Enumerator
443
+ expect( e3.next ) .to eq 123
444
+ expect( e3.next ) .to eq "this is some text"
445
+ expect{ e3.next; e3.next; e3.next } .to raise_error StopIteration
446
+
447
+ db.close
448
+ end
449
+
450
+ it "mixes in Enumerable" do
451
+ db = newdb.call(Tmp.file, "blopp")
452
+ vv = {
453
+ "foo" => 123,
454
+ "bar" => "this is some text",
455
+ "quux" => [1,2,3],
456
+ }
457
+ db.update(vv)
458
+
459
+ # Since Enumerable is pretty trusted at this point, we just test
460
+ # a smattering of methods to ensure nothing is grossly broken
461
+
462
+ expect( db.find{|k,v| k == "quux"} ) .to eq ["quux", vv["quux"]]
463
+
464
+ expect( db.map{|k,v| v} ) .to eq vv.values
465
+ expect( db.map.each{|k,v| v} ) .to eq vv.values
466
+
467
+ expect( db.first 2 ) .to eq vv.first 2
468
+
469
+ expect( db.max{|a, b| a[0].size <=> b[0].size} ) .to eq ["quux", [1,2,3]]
470
+
471
+ db.close
472
+ end
473
+
474
+ it "does select and reject (via Enumerable)" do
475
+ db = newdb.call(Tmp.file, "floop")
476
+
477
+ expect( db.select{true} ) .to eq []
478
+ expect( db.select{false} ) .to eq []
479
+ expect( db.reject{true} ) .to eq []
480
+ expect( db.reject{false} ) .to eq []
481
+
482
+ db["foo"] = 42
483
+ db["bar"] = 99
484
+ db["quux"] = 123
485
+
486
+ expect( db.select{true} ) .to eq [ ["foo", 42], ["bar", 99], ["quux", 123] ]
487
+ expect( db.select{false} ) .to eq []
488
+ expect( db.reject{false} ) .to eq [ ["foo", 42], ["bar", 99], ["quux", 123] ]
489
+ expect( db.reject{true} ) .to eq []
490
+
491
+ expect( db.select{ |k, v| v == 42 || k == "quux" } )
492
+ .to eq [ ["foo", 42], ["quux", 123] ]
493
+
494
+ expect( db.reject{ |k, v| v == 42} ) .to eq [["bar", 99], ["quux", 123]]
495
+
496
+ db.close
497
+ end
498
+
499
+ it "handles database accesses from within an 'each_*' block correctly." do
500
+ db = newdb.call(Tmp.file, "blopp")
501
+ vv = {
502
+ "foo" => 123,
503
+ "bar" => "this is some text",
504
+ "quux" => [1,2,3],
505
+ }
506
+ db.update(vv)
507
+
508
+ ev = nil
509
+ keys = []
510
+ db.each{|k,v|
511
+ keys.push k
512
+ ev = db["foo"] if k == "bar"
513
+ }
514
+ expect( keys ) .to eq vv.keys
515
+ expect( ev ) .to eq vv["foo"]
516
+
517
+ ev = nil
518
+ keys = []
519
+ db.each_key{|k|
520
+ keys.push k
521
+ ev = db["foo"] if k == "bar"
522
+ }
523
+ expect( keys ) .to eq vv.keys
524
+ expect( ev ) .to eq vv["foo"]
525
+
526
+ ev = nil
527
+ values = []
528
+ db.each_value{|v|
529
+ values.push v
530
+ ev = db["foo"] if v != 123
531
+ }
532
+ expect( values ) .to eq vv.values
533
+ expect( ev ) .to eq vv["foo"]
534
+
535
+ db.close
536
+ end
537
+
538
+
539
+ it "handles puts 'each_*' calls with blocks into their own transaction" do
540
+ db = newdb.call(Tmp.file, "blopp")
541
+ vv = {
542
+ "foo" => 123,
543
+ "bar" => "this is some text",
544
+ "quux" => [1,2,3],
545
+ }
546
+ db.update(vv)
547
+
548
+ expect( db.transaction_active? ) .to be false
549
+ db.each{
550
+ expect( db.transaction_active? ) .to be true
551
+ }
552
+ expect( db.transaction_active? ) .to be false
553
+ db.each_key{
554
+ expect( db.transaction_active? ) .to be true
555
+ }
556
+ expect( db.transaction_active? ) .to be false
557
+ db.each_value{
558
+ expect( db.transaction_active? ) .to be true
559
+ }
560
+ expect( db.transaction_active? ) .to be false
561
+
562
+ db.close
563
+ end
564
+
565
+ it "does *not* start a transaction for each_* enumerators " do
566
+ db = newdb.call(Tmp.file, "blopp")
567
+ vv = {
568
+ "foo" => 123,
569
+ "bar" => "this is some text",
570
+ "quux" => [1,2,3],
571
+ }
572
+ db.update(vv)
573
+
574
+ for method in %i{each each_key each_value}
575
+ expect( db.transaction_active? ) .to be false
576
+
577
+ e = db.send(method)
578
+ expect( db.transaction_active? ) .to be false
579
+
580
+ 3.times {
581
+ e.next
582
+ expect( db.transaction_active? ) .to be false
583
+ }
584
+
585
+ expect{ e.next } .to raise_error StopIteration
586
+ expect( db.transaction_active? ) .to be false
587
+ end
588
+
589
+ db.close
590
+ end
591
+
592
+ it "handles transaction around enumerators correctly" do
593
+ db = newdb.call(Tmp.file, "blopp")
594
+ vv = {
595
+ "foo" => 123,
596
+ "bar" => "this is some text",
597
+ "quux" => [1,2,3],
598
+ }
599
+ db.update(vv)
600
+
601
+ [
602
+ [:each, vv.to_a],
603
+ [:each_key, vv.keys ],
604
+ [:each_value, vv.values ]
605
+ ].each do | method, exp |
606
+ e = db.send(method)
607
+
608
+ # First item
609
+ expect( e.next ) .to eq exp[0]
610
+ expect( db.transaction_active? ) .to be false # redundant, but...
611
+
612
+ # An 'each' with block in the middle of the sequence doesn't
613
+ # affect the enumerator
614
+ tt = []
615
+ e.each{|a| tt.push a }
616
+ expect( tt.size ) .to eq 3
617
+
618
+ # Ditto for an 'each' on the database
619
+ tt = []
620
+ db.each{|k,v| tt.push [k,v] }
621
+ expect( tt ) .to eq vv.to_a
622
+ expect( db.transaction_active? ) .to be false
623
+
624
+ # Second item
625
+ expect( e.next ) .to eq exp[1]
626
+
627
+ # Ditto for other database reads
628
+ tmp = db["bar"]
629
+ expect( e.next ) .to eq exp[2]
630
+
631
+ # Now that we're at the end, reads raise StopIteration
632
+ expect{ e.next } .to raise_error(StopIteration)
633
+ expect{ e.next } .to raise_error(StopIteration)
634
+
635
+ # Redundant again, but why not?
636
+ expect( db.transaction_active? ) .to be false
637
+ end
638
+
639
+ db.close
640
+ end
641
+
642
+ end
643
+ end
644
+
645
+
646
+ describe Lite3::DBM do
647
+ after(:all) do
648
+ Tmp.cleanup
649
+ end
650
+
651
+ it "can also encode values using to_s" do
652
+ db = Lite3::DBM.new(Tmp.file, "tbl", :string)
653
+ vv = {
654
+ foo: 123,
655
+ bar: "this is some text",
656
+ quux: [1,2,3],
657
+ }
658
+
659
+ vv.each{|k,v| db[k] = v}
660
+
661
+ expect( db.size ) .to eq 3
662
+
663
+ vv.each{|k,v| expect( db[k] ) .to eq v.to_s }
664
+
665
+ db.close
666
+ end
667
+
668
+ it "gets tested against :marshal and :yaml" do
669
+ # Sanity test to catch if the previous block didn't do both.
670
+ expect( Serializations ) .to eq [:yaml, :marshal].to_set
671
+ end
672
+
673
+ it "provides the open-with-block semantics" do
674
+ db = nil
675
+ Lite3::DBM.open(Tmp.file, "tbl") { |dbh|
676
+ db = dbh
677
+ expect( dbh.closed? ) .to be false
678
+
679
+ dbh["foo"] = 42
680
+ expect( dbh["foo"] ) .to eq 42
681
+ }
682
+
683
+ expect( db.closed? ) .to be true
684
+ end
685
+
686
+ it "works with multiple tables in the same database file" do
687
+ vv = {
688
+ foo: 123,
689
+ bar: "this is some text",
690
+ quux: [1,2,3],
691
+ }
692
+
693
+ file = Tmp.file
694
+ dbs = (1..5).to_a.map{|i| Lite3::DBM.new(file, "tbl_#{i}") }
695
+
696
+ vv.each{|k,v|
697
+ dbs.each{|db| db[k] = v }
698
+ }
699
+
700
+ vv.each{|k,v|
701
+ dbs.each{|db|
702
+ expect( db[k] ) .to eq v
703
+ }
704
+ }
705
+
706
+ dbs.each{ |db| db.close}
707
+ end
708
+
709
+ it "handles multiple instances for the SAME table as well" do
710
+ vv = {
711
+ foo: 123,
712
+ bar: "this is some text",
713
+ quux: [1,2,3],
714
+ }
715
+
716
+ file = Tmp.file
717
+ dbs = (1..3).to_a.map{|i| Lite3::DBM.new(file, "tbl") }
718
+
719
+ vv.to_a.zip(dbs).each{ |pair, db|
720
+ k, v = pair
721
+ dbs.each{|db| db[k] = v }
722
+ }
723
+
724
+ vv.each{|k,v|
725
+ dbs.each{|db|
726
+ expect( db[k] ) .to eq v
727
+ }
728
+ }
729
+
730
+ dbs.each{ |db| db.close}
731
+ end
732
+
733
+ it "tests if the underlying SQLite3 lib was compiled to be threadsafe" do
734
+ expect( [true, false].include? Lite3::SQL.threadsafe? ) .to be true
735
+ end
736
+
737
+ it "reports on in-progress transactions" do
738
+ Lite3::DBM.open(Tmp.file, "tbl") { |dbh|
739
+ expect( dbh.transaction_active? ) .to be false
740
+ dbh.transaction {
741
+ expect( dbh.transaction_active? ) .to be true
742
+ }
743
+ }
744
+ end
745
+
746
+ it "detects incorrect serializer mixes" do
747
+ file = Tmp.file
748
+ serializers = %i{yaml marshal string}
749
+
750
+ serializers.each do |ser|
751
+ tbl = "tbl_#{ser}"
752
+ Lite3::DBM.open(file, tbl, ser) { |db|
753
+ db["foo"] = "bar"
754
+ }
755
+
756
+ # Ensure that reloading works
757
+ Lite3::DBM.open(file, tbl, ser) { |db|
758
+ expect( db.closed? ) .to be false
759
+ expect( db["foo"] ) .to eq "bar"
760
+ }
761
+
762
+ # Ensure that incompatible serializers raise an exception
763
+ alt = (serializers - [ser])[0]
764
+ expect { Lite3::DBM.new(file, tbl, alt) } .to raise_error Lite3::Error
765
+
766
+ # Ensure that it's still possible to access the DB afterward
767
+ Lite3::DBM.open(file, tbl, ser) { |db|
768
+ expect( db.closed? ) .to be false
769
+ expect( db["foo"] ) .to eq "bar"
770
+
771
+ db["bobo"] = "1"
772
+ expect( db["bobo"] ) .to eq "1"
773
+ }
774
+ end
775
+ end
776
+
777
+ it "keeps most of its names private" do
778
+ expect( Lite3.constants.to_set ) .to eq %i{SQL DBM Error}.to_set
779
+ end
780
+ end
781
+
782
+
783
+
784
+
785
+ describe Lite3::SQL do
786
+ after(:all) do
787
+ Tmp.cleanup
788
+ end
789
+
790
+ vv = {
791
+ "foo" => 123,
792
+ "bar" => "this is some text",
793
+ "quux" => [1,2,3],
794
+ }.freeze
795
+
796
+ newbasic = proc{ |file, tbl|
797
+ db = Lite3::DBM.new(file, tbl)
798
+ vv.each{|k,v| db[k] = v}
799
+ db
800
+ }
801
+
802
+ # it "manages a pool of DB handles that should now all be closed." do
803
+ # # If this fails, it (probably) means the previous tests didn't
804
+ # # clean up after themselves.
805
+ # GC.start
806
+ # expect( Lite3::SQL.gc.empty? ) .to be true
807
+
808
+ # Lite3::SQL.close_all # smoketest
809
+ # end
810
+
811
+ it "lets you close the actual handle without impeding database use" do
812
+ expect( Lite3::SQL.gc.size ) .to eq 0
813
+
814
+ file = Tmp.file
815
+ db1 = newbasic.call(file, "first")
816
+ db2 = newbasic.call(file, "second")
817
+
818
+ # The above should be using the same handle, which is currently
819
+ # open.
820
+
821
+ stats = Lite3::SQL.gc
822
+ expect( stats.keys.size ) .to eq 1
823
+
824
+ # Referencing DBM objects should be db1 and db2
825
+ path, refs = stats.to_a[0]
826
+
827
+ expect( refs.size ) .to eq 2
828
+ expect( refs.include?(db1) ) .to be true
829
+ expect( refs.include?(db2) ) .to be true
830
+
831
+ # Underlying handles should be open
832
+ expect( db1.handle_closed? ) .to be false
833
+ expect( db2.handle_closed? ) .to be false
834
+
835
+ # Test closing it
836
+ Lite3::SQL.close_all
837
+ expect( db1.handle_closed? ) .to be true
838
+ expect( db2.handle_closed? ) .to be true
839
+
840
+ # Test auto-opening them.
841
+ expect( db1["foo"] ) .to eq vv["foo"]
842
+ expect( db1.handle_closed? ) .to be false
843
+ expect( db2.handle_closed? ) .to be false
844
+
845
+ db1.close
846
+ db2.close
847
+
848
+ expect( Lite3::SQL.gc.keys.size ) .to eq 0
849
+ end
850
+
851
+ # it "(eventually) closes handles that have gone out of scope" do
852
+ # expect( Lite3::SQL.gc.keys.size ) .to eq 0
853
+
854
+ # file = Tmp.file
855
+ # db1 = newbasic.call(file, "first")
856
+
857
+ # expect( db1.handle_closed? ) .to be false
858
+ # expect( Lite3::SQL.gc.keys.size ) .to eq 1
859
+
860
+ # db1 = nil
861
+ # GC.start
862
+ # expect( Lite3::SQL.gc.keys.size ) .to eq 0
863
+ # end
864
+
865
+ it "does close_all with multiple files" do
866
+ db1 = newbasic.call(Tmp.file, "first")
867
+ db2 = newbasic.call(Tmp.file, "second")
868
+
869
+ # The above should be using the same handle, which is currently
870
+ # open.
871
+
872
+ stats = Lite3::SQL.gc
873
+ expect( stats.keys.size ) .to eq 2
874
+
875
+ all_refs = stats.values.flatten
876
+ expect( all_refs.include?(db1) ) .to be true
877
+ expect( all_refs.include?(db2) ) .to be true
878
+
879
+ # Underlying handles should be open
880
+ expect( db1.handle_closed? ) .to be false
881
+ expect( db2.handle_closed? ) .to be false
882
+
883
+ # Test closing it
884
+ Lite3::SQL.close_all
885
+ expect( db1.handle_closed? ) .to be true
886
+ expect( db2.handle_closed? ) .to be true
887
+
888
+ # Test auto-opening them.
889
+ expect( db1["foo"] ) .to eq vv["foo"]
890
+ expect( db1.handle_closed? ) .to be false
891
+ expect( db2.handle_closed? ) .to be true
892
+
893
+ db1.close
894
+ db2.close
895
+
896
+ expect( Lite3::SQL.gc.keys.size ) .to eq 0
897
+ end
898
+
899
+
900
+ it "allows multipe table accesses in the same transaction" do
901
+ file = Tmp.file
902
+ db1 = newbasic.call(file, "first")
903
+ db2 = Lite3::DBM.new(file, "second")
904
+
905
+ # The big deal is if they're part of the same file
906
+ db1.transaction {
907
+ db2.transaction {
908
+ db2.update(db1)
909
+ }
910
+ }
911
+
912
+ vv.each{ |k,v|
913
+ expect( db2[k] ) .to eq v
914
+ }
915
+
916
+ # But we should also test it across different files
917
+ db3 = Lite3::DBM.new(Tmp.file, "third")
918
+ db2.transaction {
919
+ db3.transaction {
920
+ db3.update(db2)
921
+ }
922
+ }
923
+
924
+ vv.each{ |k,v|
925
+ expect( db3[k] ) .to eq v
926
+ }
927
+
928
+ db1.close
929
+ db2.close
930
+ db3.close
931
+ end
932
+
933
+ it "gracefully catches uses of a closed handle" do
934
+ file = Tmp.file
935
+ db1 = newbasic.call(file, "first")
936
+ db1.close
937
+
938
+ # There are the only methods you can expect to work on a closed
939
+ # handle
940
+ expect( db1.closed? ) .to be true
941
+ expect( db1.to_s.class ) .to be String
942
+
943
+ # Everything else shoudl raise an error
944
+ expect{ db1["foo"] } .to raise_error Lite3::Error
945
+ expect{ db1["foo"] = 42 } .to raise_error Lite3::Error
946
+ expect{ db1.each{} } .to raise_error Lite3::Error
947
+ expect{ db1.size } .to raise_error Lite3::Error
948
+ expect{ db1.to_a } .to raise_error Lite3::Error
949
+
950
+ # Ensure we haven't accidentally overridded superclass methods.
951
+ expect( db1.object_id.class ) .to be Integer
952
+ end
953
+ end
954
+
955
+
956
+ describe self do
957
+ it "(this test) closes all handles when done with them" do
958
+ expect( Lite3::SQL.gc.size ) .to eq 0
959
+ end
960
+ end