perobs 4.0.0 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +27 -16
  3. data/lib/perobs/Array.rb +66 -19
  4. data/lib/perobs/BTree.rb +106 -15
  5. data/lib/perobs/BTreeBlob.rb +4 -3
  6. data/lib/perobs/BTreeDB.rb +5 -4
  7. data/lib/perobs/BTreeNode.rb +482 -156
  8. data/lib/perobs/BTreeNodeLink.rb +10 -0
  9. data/lib/perobs/BigArray.rb +285 -0
  10. data/lib/perobs/BigArrayNode.rb +1002 -0
  11. data/lib/perobs/BigHash.rb +246 -0
  12. data/lib/perobs/BigTree.rb +197 -0
  13. data/lib/perobs/BigTreeNode.rb +873 -0
  14. data/lib/perobs/Cache.rb +48 -10
  15. data/lib/perobs/ConsoleProgressMeter.rb +61 -0
  16. data/lib/perobs/DataBase.rb +4 -3
  17. data/lib/perobs/DynamoDB.rb +57 -15
  18. data/lib/perobs/EquiBlobsFile.rb +155 -50
  19. data/lib/perobs/FNV_Hash_1a_64.rb +54 -0
  20. data/lib/perobs/FlatFile.rb +519 -227
  21. data/lib/perobs/FlatFileBlobHeader.rb +113 -54
  22. data/lib/perobs/FlatFileDB.rb +49 -23
  23. data/lib/perobs/FuzzyStringMatcher.rb +175 -0
  24. data/lib/perobs/Hash.rb +127 -33
  25. data/lib/perobs/IDList.rb +144 -0
  26. data/lib/perobs/IDListPage.rb +107 -0
  27. data/lib/perobs/IDListPageFile.rb +180 -0
  28. data/lib/perobs/IDListPageRecord.rb +142 -0
  29. data/lib/perobs/Object.rb +18 -15
  30. data/lib/perobs/ObjectBase.rb +46 -5
  31. data/lib/perobs/PersistentObjectCache.rb +57 -68
  32. data/lib/perobs/PersistentObjectCacheLine.rb +24 -12
  33. data/lib/perobs/ProgressMeter.rb +97 -0
  34. data/lib/perobs/SpaceManager.rb +273 -0
  35. data/lib/perobs/SpaceTree.rb +21 -12
  36. data/lib/perobs/SpaceTreeNode.rb +53 -61
  37. data/lib/perobs/Store.rb +264 -145
  38. data/lib/perobs/version.rb +1 -1
  39. data/lib/perobs.rb +2 -0
  40. data/perobs.gemspec +4 -4
  41. data/test/Array_spec.rb +15 -6
  42. data/test/BTree_spec.rb +6 -2
  43. data/test/BigArray_spec.rb +261 -0
  44. data/test/BigHash_spec.rb +152 -0
  45. data/test/BigTreeNode_spec.rb +153 -0
  46. data/test/BigTree_spec.rb +259 -0
  47. data/test/EquiBlobsFile_spec.rb +105 -1
  48. data/test/FNV_Hash_1a_64_spec.rb +59 -0
  49. data/test/FlatFileDB_spec.rb +198 -14
  50. data/test/FuzzyStringMatcher_spec.rb +261 -0
  51. data/test/Hash_spec.rb +13 -3
  52. data/test/IDList_spec.rb +77 -0
  53. data/test/LegacyDBs/LegacyDB.rb +155 -0
  54. data/test/LegacyDBs/version_3/class_map.json +1 -0
  55. data/test/LegacyDBs/version_3/config.json +1 -0
  56. data/test/LegacyDBs/version_3/database.blobs +0 -0
  57. data/test/LegacyDBs/version_3/database_spaces.blobs +0 -0
  58. data/test/LegacyDBs/version_3/index.blobs +0 -0
  59. data/test/LegacyDBs/version_3/version +1 -0
  60. data/test/LockFile_spec.rb +9 -6
  61. data/test/SpaceManager_spec.rb +176 -0
  62. data/test/SpaceTree_spec.rb +4 -1
  63. data/test/Store_spec.rb +305 -203
  64. data/test/spec_helper.rb +9 -4
  65. metadata +57 -16
  66. data/lib/perobs/BTreeNodeCache.rb +0 -109
  67. data/lib/perobs/TreeDB.rb +0 -277
data/test/Store_spec.rb CHANGED
@@ -33,7 +33,7 @@ class Person < PEROBS::Object
33
33
 
34
34
  attr_persist :name, :zip, :bmi, :married, :related, :relatives
35
35
 
36
- def initialize(store)
36
+ def initialize(p)
37
37
  super
38
38
  attr_init(:name, '')
39
39
  attr_init(:bmi, 22.2)
@@ -46,7 +46,7 @@ class PersonN < PEROBS::Object
46
46
 
47
47
  attr_persist :name, :zip, :bmi, :married, :related, :relatives
48
48
 
49
- def initialize(store)
49
+ def initialize(p)
50
50
  super
51
51
  attr_init(:name, '')
52
52
  attr_init(:bmi, 22.2)
@@ -59,9 +59,9 @@ class O0 < PEROBS::Object
59
59
 
60
60
  attr_persist :child
61
61
 
62
- def initialize(store)
62
+ def initialize(p)
63
63
  super
64
- self.child = @store.new(O1, myself)
64
+ self.child = p.store.new(O1, myself)
65
65
  end
66
66
 
67
67
  end
@@ -84,22 +84,16 @@ describe PEROBS::Store do
84
84
  end
85
85
 
86
86
  after(:each) do
87
- @store.gc
88
- expect { @store.check }.to_not raise_error
89
- expect { @store.delete_store }.to_not raise_error
90
- end
91
-
92
- after(:all) do
93
87
  FileUtils.rm_rf(@db_file)
94
88
  end
95
89
 
96
90
  it 'should @store simple objects' do
97
- @store = PEROBS::Store.new(@db_file, { :serializer => :yaml })
98
- @store['john'] = john = @store.new(Person)
91
+ store = PEROBS::Store.new(@db_file, { :serializer => :yaml })
92
+ store['john'] = john = store.new(Person)
99
93
  john.name = 'John'
100
94
  john.zip = 4060
101
95
  john.bmi = 25.5
102
- @store['jane'] = jane = @store.new(Person)
96
+ store['jane'] = jane = store.new(Person)
103
97
  jane.name = 'Jane'
104
98
  jane.related = john
105
99
  jane.married = true
@@ -110,48 +104,56 @@ describe PEROBS::Store do
110
104
  expect(john.bmi).to eq(25.5)
111
105
  expect(john.married).to be false
112
106
  expect(john.related).to be_nil
113
- jane = @store['jane']
107
+ jane = store['jane']
114
108
  expect(jane.name).to eq('Jane')
115
109
  expect(jane.related).to eq(john)
116
110
  expect(jane.married).to be true
111
+
112
+ capture_io { store.gc }
113
+ capture_io { expect { store.check }.to_not raise_error }
114
+ expect { store.delete_store }.to_not raise_error
117
115
  end
118
116
 
119
117
  it 'should @store and retrieve simple objects' do
120
118
  [ :marshal, :json, :yaml ].each do |serializer|
121
119
  FileUtils.rm_rf(@db_file)
122
- @store = PEROBS::Store.new(@db_file, { :serializer => serializer })
123
- @store['john'] = john = @store.new(Person)
120
+ store = PEROBS::Store.new(@db_file, { :serializer => serializer })
121
+ store['john'] = john = store.new(Person)
124
122
  john.name = 'John'
125
123
  john.zip = 4060
126
124
  john.bmi = 25.5
127
- @store['jane'] = jane = @store.new(Person)
125
+ store['jane'] = jane = store.new(Person)
128
126
  jane.name = 'Jane'
129
127
  jane.related = john
130
128
  jane.married = true
131
129
  jane.relatives = 'test'
132
130
 
133
- @store.exit
131
+ capture_io { store.exit }
134
132
 
135
- @store = PEROBS::Store.new(@db_file)
136
- john = @store['john']
133
+ store = PEROBS::Store.new(@db_file)
134
+ john = store['john']
137
135
  expect(john.name).to eq('John')
138
136
  expect(john.zip).to eq(4060)
139
137
  expect(john.bmi).to eq(25.5)
140
138
  expect(john.married).to be false
141
139
  expect(john.related).to be_nil
142
- jane = @store['jane']
140
+ jane = store['jane']
143
141
  expect(jane.name).to eq('Jane')
144
142
  expect(jane.related).to eq(john)
145
143
  expect(jane.married).to be true
144
+
145
+ capture_io { store.gc }
146
+ capture_io { expect { store.check }.to_not raise_error }
147
+ expect { store.delete_store }.to_not raise_error
146
148
  end
147
149
  end
148
150
 
149
151
  it 'should flush cached objects when necessary' do
150
- @store = PEROBS::Store.new(@db_file, :cache_bits => 3)
152
+ store = PEROBS::Store.new(@db_file, :cache_bits => 3)
151
153
  last_obj = nil
152
154
  0.upto(20) do |i|
153
- @store["person#{i}"] = obj = @store.new(Person)
154
- expect(@store["person#{i}"]).to eq(obj)
155
+ store["person#{i}"] = obj = store.new(Person)
156
+ expect(store["person#{i}"]).to eq(obj)
155
157
  obj.name = "Person #{i}"
156
158
  expect(obj.name).to eq("Person #{i}")
157
159
  obj.related = last_obj
@@ -159,167 +161,228 @@ describe PEROBS::Store do
159
161
  last_obj = obj
160
162
  end
161
163
  0.upto(20) do |i|
162
- expect(@store["person#{i}"].name).to eq("Person #{i}")
164
+ expect(store["person#{i}"].name).to eq("Person #{i}")
163
165
  end
166
+
167
+ capture_io { store.gc }
168
+ capture_io { expect { store.check }.to_not raise_error }
169
+ expect { store.delete_store }.to_not raise_error
164
170
  end
165
171
 
166
172
  it 'should support renaming of classes' do
167
- @store = PEROBS::Store.new(@db_file)
168
- @store['john'] = john = @store.new(Person)
173
+ store = PEROBS::Store.new(@db_file)
174
+ store['john'] = john = store.new(Person)
169
175
  john.name = 'John'
170
176
  john.zip = 4060
171
177
  john.bmi = 25.5
172
- @store['jane'] = jane = @store.new(Person)
178
+ store['jane'] = jane = store.new(Person)
173
179
  jane.name = 'Jane'
174
180
  jane.related = john
175
181
  jane.married = true
176
182
  jane.relatives = 'test'
177
183
 
178
- @store.exit
184
+ capture_io { store.exit }
179
185
 
180
- @store = PEROBS::Store.new(@db_file)
181
- @store.rename_classes({ 'Person' => 'PersonN' })
182
- john = @store['john']
186
+ store = PEROBS::Store.new(@db_file)
187
+ store.rename_classes({ 'Person' => 'PersonN' })
188
+ john = store['john']
183
189
  expect(john.name).to eq('John')
184
190
  expect(john.zip).to eq(4060)
185
191
  expect(john.bmi).to eq(25.5)
186
192
  expect(john.married).to be false
187
193
  expect(john.related).to be_nil
188
- jane = @store['jane']
194
+ jane = store['jane']
189
195
  expect(jane.name).to eq('Jane')
190
196
  expect(jane.related).to eq(john)
191
197
  expect(jane.married).to be true
198
+
199
+ capture_io { store.gc }
200
+ capture_io { expect { store.check }.to_not raise_error }
201
+ expect { store.delete_store }.to_not raise_error
192
202
  end
193
203
 
194
204
  it 'should detect modification to non-working objects' do
195
- @store = PEROBS::Store.new(@db_file, :cache_bits => 3)
205
+ store = PEROBS::Store.new(@db_file, :cache_bits => 3)
196
206
  0.upto(20) do |i|
197
- @store["person#{i}"] = obj = @store.new(Person)
207
+ store["person#{i}"] = obj = store.new(Person)
198
208
  obj.name = "Person #{i}"
199
209
  end
200
210
  0.upto(20) do |i|
201
- @store["person#{i}"].name = "New Person #{i}"
211
+ store["person#{i}"].name = "New Person #{i}"
202
212
  end
203
- @store.exit
204
- @store = PEROBS::Store.new(@db_file)
213
+ capture_io { store.exit }
214
+ store = PEROBS::Store.new(@db_file)
205
215
  0.upto(20) do |i|
206
- expect(@store["person#{i}"].name).to eq("New Person #{i}")
216
+ expect(store["person#{i}"].name).to eq("New Person #{i}")
207
217
  end
218
+
219
+ capture_io { store.gc }
220
+ capture_io { expect { store.check }.to_not raise_error }
221
+ expect { store.delete_store }.to_not raise_error
208
222
  end
209
223
 
210
224
  it 'should garbage collect unlinked objects' do
211
- @store = PEROBS::Store.new(@db_file)
212
- @store['person1'] = obj = @store.new(Person)
213
- id1 = obj._id
214
- @store['person2'] = obj = @store.new(Person)
215
- id2 = obj._id
216
- obj.related = obj = @store.new(Person)
217
- id3 = obj._id
218
- @store.sync
219
- @store['person1'] = nil
220
- @store.gc
221
- @store.exit
222
- @store = PEROBS::Store.new(@db_file)
223
- expect(@store.object_by_id(id1)).to be_nil
224
- expect(@store['person2']._id).to eq(id2)
225
- expect(@store['person2'].related._id).to eq(id3)
225
+ store = PEROBS::Store.new(@db_file)
226
+ persons = []
227
+ 0.upto(20) do |i|
228
+ persons[i] = obj = store.new(Person)
229
+ obj.name = "person#{i}"
230
+ if i < 3
231
+ store["person#{i}"] = obj
232
+ else
233
+ persons[i - 3].related = obj
234
+ end
235
+ end
236
+ store.sync
237
+ expect(store.size).to eq(21)
238
+
239
+ store['person0'] = nil
240
+ capture_io { store.gc }
241
+ expect(store.size).to eq(14)
242
+ capture_io { expect { store.check }.to_not raise_error }
243
+ capture_io { store.exit }
244
+ store = PEROBS::Store.new(@db_file)
245
+ capture_io { expect { store.check }.to_not raise_error }
246
+
247
+ person = store['person1']
248
+ i = 0
249
+ while (person = person.related) do
250
+ i += 1
251
+ end
252
+ expect(i).to eq(6)
253
+
254
+ capture_io { store.gc }
255
+ capture_io { expect { store.check }.to_not raise_error }
256
+ capture_io { store.exit }
257
+
258
+ store = PEROBS::Store.new(@db_file)
259
+ capture_io { expect { store.check }.to_not raise_error }
260
+
261
+ person = store['person1']
262
+ i = 0
263
+ while (person = person.related) do
264
+ i += 1
265
+ end
266
+ expect(i).to eq(6)
267
+
268
+ capture_io { store.gc }
269
+ capture_io { expect { store.check }.to_not raise_error }
270
+ expect { store.delete_store }.to_not raise_error
226
271
  end
227
272
 
228
273
  it 'should handle cyclicly linked objects' do
229
- @store = PEROBS::Store.new(@db_file)
230
- @store['person0'] = p0 = @store.new(Person)
274
+ store = PEROBS::Store.new(@db_file)
275
+ store['person0'] = p0 = store.new(Person)
231
276
  id0 = p0._id
232
- p1 = @store.new(Person)
277
+ p1 = store.new(Person)
233
278
  id1 = p1._id
234
- p2 = @store.new(Person)
279
+ p2 = store.new(Person)
235
280
  id2 = p2._id
236
281
  p1.related = p2
237
282
  p2.related = p1
238
283
  p0.related = p1
239
- expect(@store.check).to eq(0)
240
- expect(@store.gc).to eq(0)
284
+ capture_io { expect(store.check).to eq(0) }
285
+ capture_io { expect(store.gc).to eq(0) }
241
286
  p0 = p1 = p2 = nil
242
- @store.exit
243
- GC.start
244
- @store = PEROBS::Store.new(@db_file)
245
- expect(@store['person0']._id).to eq(id0)
246
- expect(@store['person0'].related._id).to eq(id1)
247
- expect(@store['person0'].related.related._id).to eq(id2)
248
-
249
- @store['person0'].related = nil
250
- expect(@store.gc).to eq(2)
251
- GC.start
252
- expect(@store.object_by_id(id1)).to be_nil
253
- expect(@store.object_by_id(id2)).to be_nil
254
- @store.exit
255
-
256
- @store = PEROBS::Store.new(@db_file)
257
- expect(@store.check).to eq(0)
258
- expect(@store.object_by_id(id1)).to be_nil
259
- expect(@store.object_by_id(id2)).to be_nil
287
+ capture_io { store.exit }
288
+ store = PEROBS::Store.new(@db_file)
289
+ expect(store['person0']._id).to eq(id0)
290
+ expect(store['person0'].related._id).to eq(id1)
291
+ expect(store['person0'].related.related._id).to eq(id2)
292
+
293
+ store['person0'].related = nil
294
+ capture_io { expect(store.gc).to eq(2) }
295
+ stats = store.statistics
296
+ expect(stats.swept_objects).to eql(2)
297
+ capture_io { store.exit }
298
+
299
+ store = PEROBS::Store.new(@db_file)
300
+ capture_io { expect(store.check).to eq(0) }
301
+ expect(store.object_by_id(id1)).to be_nil
302
+ expect(store.object_by_id(id2)).to be_nil
303
+
304
+ capture_io { store.gc }
305
+ capture_io { expect { store.check }.to_not raise_error }
306
+ expect { store.delete_store }.to_not raise_error
260
307
  end
261
308
 
262
309
  it 'should support a successful transaction' do
263
- @store = PEROBS::Store.new(@db_file)
264
- @store.transaction do
265
- @store['person0'] = p0 = @store.new(Person)
310
+ store = PEROBS::Store.new(@db_file)
311
+ store.transaction do
312
+ store['person0'] = p0 = store.new(Person)
266
313
  p0.name = 'Jimmy'
267
314
  end
268
- expect(@store['person0'].name).to eq('Jimmy')
315
+ expect(store['person0'].name).to eq('Jimmy')
316
+
317
+ capture_io { store.gc }
318
+ capture_io { expect { store.check }.to_not raise_error }
319
+ expect { store.delete_store }.to_not raise_error
269
320
  end
270
321
 
271
322
  it 'should handle a failed transaction 1' do
272
- @store = PEROBS::Store.new(@db_file)
323
+ store = PEROBS::Store.new(@db_file)
273
324
  begin
274
- @store.transaction do
275
- @store['person0'] = p0 = @store.new(Person)
325
+ store.transaction do
326
+ store['person0'] = p0 = store.new(Person)
276
327
  p0.name = 'Jimmy'
277
328
  raise POSError
278
329
  end
279
330
  rescue POSError
280
331
  end
281
- expect(@store['person0']).to be_nil
332
+ expect(store['person0']).to be_nil
333
+
334
+ capture_io { store.gc }
335
+ capture_io { expect { store.check }.to_not raise_error }
336
+ expect { store.delete_store }.to_not raise_error
282
337
  end
283
338
 
284
339
  it 'should handle a failed transaction 2' do
285
- @store = PEROBS::Store.new(@db_file)
286
- @store['person1'] = p1 = @store.new(Person)
340
+ store = PEROBS::Store.new(@db_file)
341
+ store['person1'] = p1 = store.new(Person)
287
342
  p1.name = 'Joe'
288
343
  begin
289
- @store.transaction do
290
- @store['person0'] = p0 = @store.new(Person)
344
+ store.transaction do
345
+ store['person0'] = p0 = store.new(Person)
291
346
  p0.name = 'Jimmy'
292
347
  raise POSError
293
348
  end
294
349
  rescue POSError
295
350
  end
296
- expect(@store['person1'].name).to eq('Joe')
297
- expect(@store['person0']).to be_nil
351
+ expect(store['person1'].name).to eq('Joe')
352
+ expect(store['person0']).to be_nil
353
+
354
+ capture_io { store.gc }
355
+ capture_io { expect { store.check }.to_not raise_error }
356
+ expect { store.delete_store }.to_not raise_error
298
357
  end
299
358
 
300
359
  it 'should support a successful nested transaction' do
301
- @store = PEROBS::Store.new(@db_file)
302
- @store.transaction do
303
- @store['person0'] = p0 = @store.new(Person)
360
+ store = PEROBS::Store.new(@db_file)
361
+ store.transaction do
362
+ store['person0'] = p0 = store.new(Person)
304
363
  p0.name = 'Jimmy'
305
- @store.transaction do
306
- @store['person1'] = p1 = @store.new(Person)
364
+ store.transaction do
365
+ store['person1'] = p1 = store.new(Person)
307
366
  p1.name = 'Joe'
308
367
  end
309
368
  end
310
- expect(@store['person0'].name).to eq('Jimmy')
311
- expect(@store['person1'].name).to eq('Joe')
369
+ expect(store['person0'].name).to eq('Jimmy')
370
+ expect(store['person1'].name).to eq('Joe')
371
+
372
+ capture_io { store.gc }
373
+ capture_io { expect { store.check }.to_not raise_error }
374
+ expect { store.delete_store }.to_not raise_error
312
375
  end
313
376
 
314
377
  it 'should handle a failed nested transaction 1' do
315
- @store = PEROBS::Store.new(@db_file)
378
+ store = PEROBS::Store.new(@db_file)
316
379
  begin
317
- @store.transaction do
318
- @store['person0'] = p0 = @store.new(Person)
380
+ store.transaction do
381
+ store['person0'] = p0 = store.new(Person)
319
382
  p0.name = 'Jimmy'
320
383
  begin
321
- @store.transaction do
322
- @store['person1'] = p1 = @store.new(Person)
384
+ store.transaction do
385
+ store['person1'] = p1 = store.new(Person)
323
386
  p1.name = 'Joe'
324
387
  raise POSError
325
388
  end
@@ -328,58 +391,70 @@ describe PEROBS::Store do
328
391
  end
329
392
  rescue POSError
330
393
  end
331
- expect(@store['person0'].name).to eq('Jimmy')
332
- expect(@store['person1']).to be_nil
394
+ expect(store['person0'].name).to eq('Jimmy')
395
+ expect(store['person1']).to be_nil
396
+
397
+ capture_io { store.gc }
398
+ capture_io { expect { store.check }.to_not raise_error }
399
+ expect { store.delete_store }.to_not raise_error
333
400
  end
334
401
 
335
402
  it 'should handle a failed nested transaction 2' do
336
- @store = PEROBS::Store.new(@db_file)
403
+ store = PEROBS::Store.new(@db_file)
337
404
  begin
338
- @store.transaction do
339
- @store['person0'] = p0 = @store.new(Person)
405
+ store.transaction do
406
+ store['person0'] = p0 = store.new(Person)
340
407
  p0.name = 'Jimmy'
341
- @store.transaction do
342
- @store['person1'] = p1 = @store.new(Person)
408
+ store.transaction do
409
+ store['person1'] = p1 = store.new(Person)
343
410
  p1.name = 'Joe'
344
411
  end
345
412
  raise POSError
346
413
  end
347
414
  rescue POSError
348
415
  end
349
- expect(@store['person0']).to be_nil
350
- expect(@store['person1']).to be_nil
416
+ expect(store['person0']).to be_nil
417
+ expect(store['person1']).to be_nil
418
+
419
+ capture_io { store.gc }
420
+ capture_io { expect { store.check }.to_not raise_error }
421
+ expect { store.delete_store }.to_not raise_error
351
422
  end
352
423
 
353
424
  it 'should support a successful 2-level nested transaction' do
354
- @store = PEROBS::Store.new(@db_file)
355
- @store.transaction do
356
- @store['person0'] = p0 = @store.new(Person)
425
+ store = PEROBS::Store.new(@db_file)
426
+ store.transaction do
427
+ store['person0'] = p0 = store.new(Person)
357
428
  p0.name = 'Jimmy'
358
- @store.transaction do
359
- @store['person1'] = p1 = @store.new(Person)
429
+ store.transaction do
430
+ store['person1'] = p1 = store.new(Person)
360
431
  p1.name = 'Joe'
361
- @store.transaction do
362
- @store['person2'] = p2 = @store.new(Person)
432
+ store.transaction do
433
+ store['person2'] = p2 = store.new(Person)
363
434
  p2.name = 'Jane'
364
435
  end
365
436
  end
366
437
  end
367
- expect(@store['person0'].name).to eq('Jimmy')
368
- expect(@store['person1'].name).to eq('Joe')
369
- expect(@store['person2'].name).to eq('Jane')
438
+ expect(store['person0'].name).to eq('Jimmy')
439
+ expect(store['person1'].name).to eq('Joe')
440
+ expect(store['person2'].name).to eq('Jane')
441
+
442
+ capture_io { store.gc }
443
+ capture_io { expect { store.check }.to_not raise_error }
444
+ expect { store.delete_store }.to_not raise_error
370
445
  end
371
446
 
372
447
  it 'should handle a failed 2-level nested transaction 1' do
373
- @store = PEROBS::Store.new(@db_file)
374
- @store.transaction do
375
- @store['person0'] = p0 = @store.new(Person)
448
+ store = PEROBS::Store.new(@db_file)
449
+ store.transaction do
450
+ store['person0'] = p0 = store.new(Person)
376
451
  p0.name = 'Jimmy'
377
- @store.transaction do
378
- @store['person1'] = p1 = @store.new(Person)
452
+ store.transaction do
453
+ store['person1'] = p1 = store.new(Person)
379
454
  p1.name = 'Joe'
380
455
  begin
381
- @store.transaction do
382
- @store['person2'] = p2 = @store.new(Person)
456
+ store.transaction do
457
+ store['person2'] = p2 = store.new(Person)
383
458
  p2.name = 'Jane'
384
459
  raise POSError
385
460
  end
@@ -387,22 +462,26 @@ describe PEROBS::Store do
387
462
  end
388
463
  end
389
464
  end
390
- expect(@store['person0'].name).to eq('Jimmy')
391
- expect(@store['person1'].name).to eq('Joe')
392
- expect(@store['person2']).to be_nil
465
+ expect(store['person0'].name).to eq('Jimmy')
466
+ expect(store['person1'].name).to eq('Joe')
467
+ expect(store['person2']).to be_nil
468
+
469
+ capture_io { store.gc }
470
+ capture_io { expect { store.check }.to_not raise_error }
471
+ expect { store.delete_store }.to_not raise_error
393
472
  end
394
473
 
395
474
  it 'should handle a failed 2-level nested transaction 2' do
396
- @store = PEROBS::Store.new(@db_file)
397
- @store.transaction do
398
- @store['person0'] = p0 = @store.new(Person)
475
+ store = PEROBS::Store.new(@db_file)
476
+ store.transaction do
477
+ store['person0'] = p0 = store.new(Person)
399
478
  p0.name = 'Jimmy'
400
- @store.transaction do
401
- @store['person1'] = p1 = @store.new(Person)
479
+ store.transaction do
480
+ store['person1'] = p1 = store.new(Person)
402
481
  p1.name = 'Joe'
403
482
  begin
404
- @store.transaction do
405
- @store['person2'] = p2 = @store.new(Person)
483
+ store.transaction do
484
+ store['person2'] = p2 = store.new(Person)
406
485
  p2.name = 'Jane'
407
486
  raise POSError
408
487
  end
@@ -411,55 +490,66 @@ describe PEROBS::Store do
411
490
  p1.name = 'Jane'
412
491
  end
413
492
  end
414
- expect(@store['person0'].name).to eq('Jimmy')
415
- expect(@store['person1'].name).to eq('Jane')
416
- expect(@store['person2']).to be_nil
493
+ expect(store['person0'].name).to eq('Jimmy')
494
+ expect(store['person1'].name).to eq('Jane')
495
+ expect(store['person2']).to be_nil
496
+
497
+ capture_io { store.gc }
498
+ capture_io { expect { store.check }.to_not raise_error }
499
+ expect { store.delete_store }.to_not raise_error
417
500
  end
418
501
 
419
502
  it 'should track in-memory objects properly' do
420
- @store = PEROBS::Store.new(@db_file)
421
- expect(@store.statistics[:in_memory_objects]).to eq(1)
422
- @store['person'] = @store.new(Person)
503
+ store = PEROBS::Store.new(@db_file)
504
+ expect(store.statistics[:in_memory_objects]).to eq(1)
505
+ store['person'] = store.new(Person)
423
506
  # We have the root hash and the Person object.
424
- expect(@store.statistics[:in_memory_objects]).to eq(2)
425
- @store.sync
426
- GC.start
507
+ expect(store.statistics[:in_memory_objects]).to eq(2)
508
+ store.sync
427
509
  # Now the Person should be gone from memory.
428
510
  # Ruby 2.3 and later has changed the GC so that this does not work
429
511
  # reliably anymore. The GC seems to operate lazyly.
430
- #expect(@store.statistics[:in_memory_objects]).to eq(1)
512
+ #expect(store.statistics[:in_memory_objects]).to eq(1)
513
+
514
+ capture_io { store.gc }
515
+ capture_io { expect { store.check }.to_not raise_error }
516
+ expect { store.delete_store }.to_not raise_error
431
517
  end
432
518
 
433
519
  it 'should handle nested constructors' do
434
- @store = PEROBS::Store.new(@db_file)
435
- @store['root'] = @store.new(O0)
436
- @store.sync
437
- expect(@store.check).to eq(0)
438
- @store.exit
439
-
440
- @store = PEROBS::Store.new(@db_file)
441
- expect(@store.check).to eq(0)
442
- expect(@store['root'].child.parent).to eq(@store['root'])
520
+ store = PEROBS::Store.new(@db_file)
521
+ store['root'] = store.new(O0)
522
+ store.sync
523
+ capture_io { expect(store.check).to eq(0) }
524
+ capture_io { store.exit }
525
+
526
+ store = PEROBS::Store.new(@db_file)
527
+ capture_io { expect(store.check).to eq(0) }
528
+ expect(store['root'].child.parent).to eq(store['root'])
529
+
530
+ capture_io { store.gc }
531
+ capture_io { expect { store.check }.to_not raise_error }
532
+ expect { store.delete_store }.to_not raise_error
443
533
  end
444
534
 
445
535
  it 'should handle frequent updates of objects' do
446
- @store = PEROBS::Store.new(@db_file)
536
+ store = PEROBS::Store.new(@db_file)
447
537
  count = 10000
448
538
  0.upto(count) do |i|
449
539
  key = "Obj#{i}"
450
- @store[key] = p = @store.new(Person)
540
+ store[key] = p = store.new(Person)
451
541
  p.name = "0:#{i}:" + 'X' * rand(64)
452
542
  end
453
543
 
454
544
  0.upto(10) do |iteration|
455
545
  0.upto(count) do |i|
456
546
  key = "Obj#{i}"
457
- p = @store[key]
547
+ p = store[key]
458
548
  p.name = "#{iteration}:#{i}:" + 'X' * rand(64)
459
549
  end
460
550
  0.upto(count) do |i|
461
551
  key = "Obj#{i}"
462
- p = @store[key]
552
+ p = store[key]
463
553
  o_it, o_i, o_x = p.name.split(':')
464
554
  if o_it.to_i != iteration
465
555
  $stderr.puts "Mismatch of #{p._id} with value #{o_it}:#{i}"
@@ -467,37 +557,41 @@ describe PEROBS::Store do
467
557
  expect(o_it.to_i).to eql(iteration)
468
558
  expect(o_i.to_i).to eql(i)
469
559
  end
470
- @store.check
560
+ capture_io { expect(store.check).to eql(0) }
471
561
  end
562
+
563
+ capture_io { store.gc }
564
+ capture_io { expect { store.check }.to_not raise_error }
565
+ expect { store.delete_store }.to_not raise_error
472
566
  end
473
567
 
474
568
  it 'should survive a real world usage test' do
475
569
  options = { :engine => PEROBS::FlatFileDB }
476
- @store = PEROBS::Store.new(@db_file, options)
570
+ store = PEROBS::Store.new(@db_file, options)
477
571
  ref = {}
478
572
 
479
573
  deletions_since_last_gc = 0
480
- 0.upto(10000) do |i|
574
+ 0.upto(5000) do |i|
481
575
  key = "o#{i}"
482
576
  case rand(9)
483
577
  when 0
484
578
  # Add 'A' person
485
579
  value = key + 'A' * rand(512)
486
- @store[key] = p = @store.new(Person)
580
+ store[key] = p = store.new(Person)
487
581
  p.name = value
488
582
  ref[key] = value
489
583
  when 1
490
584
  # Add 'B' person
491
585
  value = key + 'B' * rand(32)
492
- @store[key] = p = @store.new(Person)
586
+ store[key] = p = store.new(Person)
493
587
  p.name = value
494
588
  ref[key] = value
495
589
  when 2
496
590
  # Delete a root entry
497
591
  if ref.keys.length > 11
498
592
  key = ref.keys[rand(ref.keys.length)]
499
- expect(@store[key]).not_to be_nil
500
- @store[key] = nil
593
+ expect(store[key]).not_to be_nil
594
+ store[key] = nil
501
595
  ref.delete(key)
502
596
  deletions_since_last_gc += 1
503
597
  end
@@ -505,34 +599,34 @@ describe PEROBS::Store do
505
599
  # Update a person entry
506
600
  if ref.keys.length > 0
507
601
  key = ref.keys[rand(ref.keys.length)]
508
- expect(@store[key]).not_to be_nil
602
+ expect(store[key]).not_to be_nil
509
603
  value = key + 'C' * rand(996)
510
- p = @store[key]
604
+ p = store[key]
511
605
  p.name = value
512
606
  ref[key] = value
513
607
  end
514
608
  when 4
515
609
  # Call garbage collector
516
610
  if rand(60) == 0
517
- @store.gc
518
- stats = @store.statistics
611
+ capture_io { store.gc }
612
+ stats = store.statistics
519
613
  expect(stats.marked_objects).to eq(ref.length)
520
614
  expect(stats.swept_objects).to eq(deletions_since_last_gc)
521
615
  deletions_since_last_gc = 0
522
- expect(@store.gc).to eq(deletions_since_last_gc)
616
+ capture_io { expect(store.gc).to eq(deletions_since_last_gc) }
523
617
  end
524
618
  when 5
525
619
  # Sync store and reload
526
620
  if rand(15) == 0
527
- @store.exit
528
- @store = PEROBS::Store.new(@db_file, options)
621
+ capture_io { store.exit }
622
+ store = PEROBS::Store.new(@db_file, options)
529
623
  end
530
624
  when 6
531
625
  # Replace an entry with 'C' person
532
626
  if ref.keys.length > 13
533
627
  key = ref.keys[(ref.keys.length / 13).to_i]
534
628
  value = key + 'D' * rand(1024)
535
- @store[key] = p = @store.new(Person)
629
+ store[key] = p = store.new(Person)
536
630
  p.name = value
537
631
  ref[key] = value
538
632
  deletions_since_last_gc += 1
@@ -540,57 +634,65 @@ describe PEROBS::Store do
540
634
  when 7
541
635
  # Sync and check store
542
636
  if rand(50) == 0
543
- #@store.sync
544
- expect(@store.check(false)).to eq(0)
637
+ #store.sync
638
+ capture_io { expect(store.check(false)).to eq(0) }
545
639
  end
546
640
  when 8
547
641
  # Compare a random entry with reference entry
548
642
  if ref.keys.length > 0
549
643
  key = ref.keys[rand(ref.keys.length)]
550
- expect(@store[key].name).to eq(ref[key])
644
+ expect(store[key].name).to eq(ref[key])
551
645
  end
552
646
  end
553
647
  #ref.each do |k, v|
554
- # expect(@store[k].name).to eq(v), "failure in mode #{i}"
648
+ # expect(store[k].name).to eq(v), "failure in mode #{i}"
555
649
  #end
556
650
  end
557
651
 
558
652
  ref.each do |k, v|
559
- expect(@store[k].name).to eq(v)
653
+ expect(store[k].name).to eq(v)
560
654
  end
655
+
656
+ capture_io { store.gc }
657
+ capture_io { expect { store.check }.to_not raise_error }
658
+ expect { store.delete_store }.to_not raise_error
561
659
  end
562
660
 
563
661
  it 'should copy the database' do
564
- @store = PEROBS::Store.new(@db_file)
565
- @store['person0'] = p0 = @store.new(Person)
662
+ store = PEROBS::Store.new(@db_file)
663
+ store['person0'] = p0 = store.new(Person)
566
664
  id0 = p0._id
567
- p1 = @store.new(Person)
665
+ p1 = store.new(Person)
568
666
  id1 = p1._id
569
- p2 = @store.new(Person)
667
+ p2 = store.new(Person)
570
668
  id2 = p2._id
571
669
  p1.related = p2
572
670
  p2.related = p1
573
671
  p0.related = p1
574
- p3 = @store.new(PEROBS::Array)
575
- @store['persons'] = p3
672
+ p3 = store.new(PEROBS::Array)
673
+ store['persons'] = p3
576
674
  p3 << p0
577
675
  p3 << p1
578
676
  p3 << p2
579
677
  p0 = p1 = p2 = p3 = nil
580
- expect(@store['person0']._id).to eq(id0)
581
- expect(@store['person0'].related._id).to eq(id1)
582
- expect(@store['person0'].related.related._id).to eq(id2)
583
-
584
- @store.copy(@db_file_new, { :engine => PEROBS::FlatFileDB })
585
- @store.delete_store
586
-
587
- @store = PEROBS::Store.new(@db_file_new, { :engine => PEROBS::FlatFileDB })
588
- expect(@store['person0']._id).to eq(id0)
589
- expect(@store['person0'].related._id).to eq(id1)
590
- expect(@store['person0'].related.related._id).to eq(id2)
591
- expect(@store['persons'][0]).to eq(@store['person0'])
592
- expect(@store['persons'][1]).to eq(@store['person0'].related)
593
- expect(@store['persons'][2]).to eq(@store['person0'].related.related)
678
+ expect(store['person0']._id).to eq(id0)
679
+ expect(store['person0'].related._id).to eq(id1)
680
+ expect(store['person0'].related.related._id).to eq(id2)
681
+
682
+ store.copy(@db_file_new, { :engine => PEROBS::FlatFileDB })
683
+ store.delete_store
684
+
685
+ store = PEROBS::Store.new(@db_file_new, { :engine => PEROBS::FlatFileDB })
686
+ expect(store['person0']._id).to eq(id0)
687
+ expect(store['person0'].related._id).to eq(id1)
688
+ expect(store['person0'].related.related._id).to eq(id2)
689
+ expect(store['persons'][0]).to eq(store['person0'])
690
+ expect(store['persons'][1]).to eq(store['person0'].related)
691
+ expect(store['persons'][2]).to eq(store['person0'].related.related)
692
+
693
+ capture_io { store.gc }
694
+ capture_io { expect { store.check }.to_not raise_error }
695
+ expect { store.delete_store }.to_not raise_error
594
696
  end
595
697
 
596
698
  end