perobs 4.0.0 → 4.1.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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/lib/perobs.rb +1 -0
  3. data/lib/perobs/Array.rb +66 -19
  4. data/lib/perobs/BTree.rb +83 -12
  5. data/lib/perobs/BTreeBlob.rb +1 -1
  6. data/lib/perobs/BTreeDB.rb +2 -2
  7. data/lib/perobs/BTreeNode.rb +365 -85
  8. data/lib/perobs/BigArray.rb +267 -0
  9. data/lib/perobs/BigArrayNode.rb +998 -0
  10. data/lib/perobs/BigHash.rb +262 -0
  11. data/lib/perobs/BigTree.rb +184 -0
  12. data/lib/perobs/BigTreeNode.rb +873 -0
  13. data/lib/perobs/ConsoleProgressMeter.rb +61 -0
  14. data/lib/perobs/DataBase.rb +4 -3
  15. data/lib/perobs/DynamoDB.rb +57 -15
  16. data/lib/perobs/EquiBlobsFile.rb +143 -51
  17. data/lib/perobs/FNV_Hash_1a_64.rb +54 -0
  18. data/lib/perobs/FlatFile.rb +363 -203
  19. data/lib/perobs/FlatFileBlobHeader.rb +98 -54
  20. data/lib/perobs/FlatFileDB.rb +42 -20
  21. data/lib/perobs/Hash.rb +58 -13
  22. data/lib/perobs/IDList.rb +144 -0
  23. data/lib/perobs/IDListPage.rb +107 -0
  24. data/lib/perobs/IDListPageFile.rb +180 -0
  25. data/lib/perobs/IDListPageRecord.rb +142 -0
  26. data/lib/perobs/Object.rb +18 -15
  27. data/lib/perobs/ObjectBase.rb +38 -4
  28. data/lib/perobs/PersistentObjectCache.rb +53 -67
  29. data/lib/perobs/PersistentObjectCacheLine.rb +24 -12
  30. data/lib/perobs/ProgressMeter.rb +97 -0
  31. data/lib/perobs/SpaceTree.rb +21 -12
  32. data/lib/perobs/SpaceTreeNode.rb +53 -61
  33. data/lib/perobs/Store.rb +71 -32
  34. data/lib/perobs/version.rb +1 -1
  35. data/perobs.gemspec +4 -4
  36. data/test/Array_spec.rb +15 -6
  37. data/test/BTree_spec.rb +5 -2
  38. data/test/BigArray_spec.rb +214 -0
  39. data/test/BigHash_spec.rb +144 -0
  40. data/test/BigTreeNode_spec.rb +153 -0
  41. data/test/BigTree_spec.rb +259 -0
  42. data/test/EquiBlobsFile_spec.rb +105 -1
  43. data/test/FNV_Hash_1a_64_spec.rb +59 -0
  44. data/test/FlatFileDB_spec.rb +63 -14
  45. data/test/Hash_spec.rb +1 -2
  46. data/test/IDList_spec.rb +77 -0
  47. data/test/LegacyDBs/LegacyDB.rb +151 -0
  48. data/test/LegacyDBs/version_3/class_map.json +1 -0
  49. data/test/LegacyDBs/version_3/config.json +1 -0
  50. data/test/LegacyDBs/version_3/database.blobs +0 -0
  51. data/test/LegacyDBs/version_3/database_spaces.blobs +0 -0
  52. data/test/LegacyDBs/version_3/index.blobs +0 -0
  53. data/test/LegacyDBs/version_3/version +1 -0
  54. data/test/LockFile_spec.rb +9 -6
  55. data/test/SpaceTree_spec.rb +4 -1
  56. data/test/Store_spec.rb +290 -199
  57. data/test/spec_helper.rb +9 -4
  58. metadata +47 -10
  59. data/lib/perobs/TreeDB.rb +0 -277
@@ -27,13 +27,16 @@ require 'fileutils'
27
27
 
28
28
  require 'spec_helper'
29
29
  require 'perobs/SpaceTree'
30
+ require 'perobs/ProgressMeter'
30
31
 
31
32
  describe PEROBS::SpaceTree do
32
33
 
33
34
  before(:all) do
34
35
  @db_dir = generate_db_name('SpaceTree')
35
36
  FileUtils.mkdir_p(@db_dir)
36
- @m = PEROBS::SpaceTree.new(@db_dir)
37
+ @m = PEROBS::SpaceTree.new(@db_dir, PEROBS::ProgressMeter.new)
38
+ PEROBS.log.level = Logger::ERROR
39
+ PEROBS.log.open($stderr)
37
40
  end
38
41
 
39
42
  after(:all) do
@@ -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,216 @@ 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
+ expect { store.delete_store }.to_not raise_error
226
257
  end
227
258
 
228
259
  it 'should handle cyclicly linked objects' do
229
- @store = PEROBS::Store.new(@db_file)
230
- @store['person0'] = p0 = @store.new(Person)
260
+ store = PEROBS::Store.new(@db_file)
261
+ store['person0'] = p0 = store.new(Person)
231
262
  id0 = p0._id
232
- p1 = @store.new(Person)
263
+ p1 = store.new(Person)
233
264
  id1 = p1._id
234
- p2 = @store.new(Person)
265
+ p2 = store.new(Person)
235
266
  id2 = p2._id
236
267
  p1.related = p2
237
268
  p2.related = p1
238
269
  p0.related = p1
239
- expect(@store.check).to eq(0)
240
- expect(@store.gc).to eq(0)
270
+ capture_io { expect(store.check).to eq(0) }
271
+ capture_io { expect(store.gc).to eq(0) }
241
272
  p0 = p1 = p2 = nil
242
- @store.exit
273
+ capture_io { store.exit }
243
274
  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)
275
+ store = PEROBS::Store.new(@db_file)
276
+ expect(store['person0']._id).to eq(id0)
277
+ expect(store['person0'].related._id).to eq(id1)
278
+ expect(store['person0'].related.related._id).to eq(id2)
248
279
 
249
- @store['person0'].related = nil
250
- expect(@store.gc).to eq(2)
280
+ store['person0'].related = nil
281
+ capture_io { expect(store.gc).to eq(2) }
251
282
  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
283
+ expect(store.object_by_id(id1)).to be_nil
284
+ expect(store.object_by_id(id2)).to be_nil
285
+ capture_io { store.exit }
286
+
287
+ store = PEROBS::Store.new(@db_file)
288
+ capture_io { expect(store.check).to eq(0) }
289
+ expect(store.object_by_id(id1)).to be_nil
290
+ expect(store.object_by_id(id2)).to be_nil
291
+
292
+ capture_io { store.gc }
293
+ capture_io { expect { store.check }.to_not raise_error }
294
+ expect { store.delete_store }.to_not raise_error
260
295
  end
261
296
 
262
297
  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)
298
+ store = PEROBS::Store.new(@db_file)
299
+ store.transaction do
300
+ store['person0'] = p0 = store.new(Person)
266
301
  p0.name = 'Jimmy'
267
302
  end
268
- expect(@store['person0'].name).to eq('Jimmy')
303
+ expect(store['person0'].name).to eq('Jimmy')
304
+
305
+ capture_io { store.gc }
306
+ capture_io { expect { store.check }.to_not raise_error }
307
+ expect { store.delete_store }.to_not raise_error
269
308
  end
270
309
 
271
310
  it 'should handle a failed transaction 1' do
272
- @store = PEROBS::Store.new(@db_file)
311
+ store = PEROBS::Store.new(@db_file)
273
312
  begin
274
- @store.transaction do
275
- @store['person0'] = p0 = @store.new(Person)
313
+ store.transaction do
314
+ store['person0'] = p0 = store.new(Person)
276
315
  p0.name = 'Jimmy'
277
316
  raise POSError
278
317
  end
279
318
  rescue POSError
280
319
  end
281
- expect(@store['person0']).to be_nil
320
+ expect(store['person0']).to be_nil
321
+
322
+ capture_io { store.gc }
323
+ capture_io { expect { store.check }.to_not raise_error }
324
+ expect { store.delete_store }.to_not raise_error
282
325
  end
283
326
 
284
327
  it 'should handle a failed transaction 2' do
285
- @store = PEROBS::Store.new(@db_file)
286
- @store['person1'] = p1 = @store.new(Person)
328
+ store = PEROBS::Store.new(@db_file)
329
+ store['person1'] = p1 = store.new(Person)
287
330
  p1.name = 'Joe'
288
331
  begin
289
- @store.transaction do
290
- @store['person0'] = p0 = @store.new(Person)
332
+ store.transaction do
333
+ store['person0'] = p0 = store.new(Person)
291
334
  p0.name = 'Jimmy'
292
335
  raise POSError
293
336
  end
294
337
  rescue POSError
295
338
  end
296
- expect(@store['person1'].name).to eq('Joe')
297
- expect(@store['person0']).to be_nil
339
+ expect(store['person1'].name).to eq('Joe')
340
+ expect(store['person0']).to be_nil
341
+
342
+ capture_io { store.gc }
343
+ capture_io { expect { store.check }.to_not raise_error }
344
+ expect { store.delete_store }.to_not raise_error
298
345
  end
299
346
 
300
347
  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)
348
+ store = PEROBS::Store.new(@db_file)
349
+ store.transaction do
350
+ store['person0'] = p0 = store.new(Person)
304
351
  p0.name = 'Jimmy'
305
- @store.transaction do
306
- @store['person1'] = p1 = @store.new(Person)
352
+ store.transaction do
353
+ store['person1'] = p1 = store.new(Person)
307
354
  p1.name = 'Joe'
308
355
  end
309
356
  end
310
- expect(@store['person0'].name).to eq('Jimmy')
311
- expect(@store['person1'].name).to eq('Joe')
357
+ expect(store['person0'].name).to eq('Jimmy')
358
+ expect(store['person1'].name).to eq('Joe')
359
+
360
+ capture_io { store.gc }
361
+ capture_io { expect { store.check }.to_not raise_error }
362
+ expect { store.delete_store }.to_not raise_error
312
363
  end
313
364
 
314
365
  it 'should handle a failed nested transaction 1' do
315
- @store = PEROBS::Store.new(@db_file)
366
+ store = PEROBS::Store.new(@db_file)
316
367
  begin
317
- @store.transaction do
318
- @store['person0'] = p0 = @store.new(Person)
368
+ store.transaction do
369
+ store['person0'] = p0 = store.new(Person)
319
370
  p0.name = 'Jimmy'
320
371
  begin
321
- @store.transaction do
322
- @store['person1'] = p1 = @store.new(Person)
372
+ store.transaction do
373
+ store['person1'] = p1 = store.new(Person)
323
374
  p1.name = 'Joe'
324
375
  raise POSError
325
376
  end
@@ -328,58 +379,70 @@ describe PEROBS::Store do
328
379
  end
329
380
  rescue POSError
330
381
  end
331
- expect(@store['person0'].name).to eq('Jimmy')
332
- expect(@store['person1']).to be_nil
382
+ expect(store['person0'].name).to eq('Jimmy')
383
+ expect(store['person1']).to be_nil
384
+
385
+ capture_io { store.gc }
386
+ capture_io { expect { store.check }.to_not raise_error }
387
+ expect { store.delete_store }.to_not raise_error
333
388
  end
334
389
 
335
390
  it 'should handle a failed nested transaction 2' do
336
- @store = PEROBS::Store.new(@db_file)
391
+ store = PEROBS::Store.new(@db_file)
337
392
  begin
338
- @store.transaction do
339
- @store['person0'] = p0 = @store.new(Person)
393
+ store.transaction do
394
+ store['person0'] = p0 = store.new(Person)
340
395
  p0.name = 'Jimmy'
341
- @store.transaction do
342
- @store['person1'] = p1 = @store.new(Person)
396
+ store.transaction do
397
+ store['person1'] = p1 = store.new(Person)
343
398
  p1.name = 'Joe'
344
399
  end
345
400
  raise POSError
346
401
  end
347
402
  rescue POSError
348
403
  end
349
- expect(@store['person0']).to be_nil
350
- expect(@store['person1']).to be_nil
404
+ expect(store['person0']).to be_nil
405
+ expect(store['person1']).to be_nil
406
+
407
+ capture_io { store.gc }
408
+ capture_io { expect { store.check }.to_not raise_error }
409
+ expect { store.delete_store }.to_not raise_error
351
410
  end
352
411
 
353
412
  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)
413
+ store = PEROBS::Store.new(@db_file)
414
+ store.transaction do
415
+ store['person0'] = p0 = store.new(Person)
357
416
  p0.name = 'Jimmy'
358
- @store.transaction do
359
- @store['person1'] = p1 = @store.new(Person)
417
+ store.transaction do
418
+ store['person1'] = p1 = store.new(Person)
360
419
  p1.name = 'Joe'
361
- @store.transaction do
362
- @store['person2'] = p2 = @store.new(Person)
420
+ store.transaction do
421
+ store['person2'] = p2 = store.new(Person)
363
422
  p2.name = 'Jane'
364
423
  end
365
424
  end
366
425
  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')
426
+ expect(store['person0'].name).to eq('Jimmy')
427
+ expect(store['person1'].name).to eq('Joe')
428
+ expect(store['person2'].name).to eq('Jane')
429
+
430
+ capture_io { store.gc }
431
+ capture_io { expect { store.check }.to_not raise_error }
432
+ expect { store.delete_store }.to_not raise_error
370
433
  end
371
434
 
372
435
  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)
436
+ store = PEROBS::Store.new(@db_file)
437
+ store.transaction do
438
+ store['person0'] = p0 = store.new(Person)
376
439
  p0.name = 'Jimmy'
377
- @store.transaction do
378
- @store['person1'] = p1 = @store.new(Person)
440
+ store.transaction do
441
+ store['person1'] = p1 = store.new(Person)
379
442
  p1.name = 'Joe'
380
443
  begin
381
- @store.transaction do
382
- @store['person2'] = p2 = @store.new(Person)
444
+ store.transaction do
445
+ store['person2'] = p2 = store.new(Person)
383
446
  p2.name = 'Jane'
384
447
  raise POSError
385
448
  end
@@ -387,22 +450,26 @@ describe PEROBS::Store do
387
450
  end
388
451
  end
389
452
  end
390
- expect(@store['person0'].name).to eq('Jimmy')
391
- expect(@store['person1'].name).to eq('Joe')
392
- expect(@store['person2']).to be_nil
453
+ expect(store['person0'].name).to eq('Jimmy')
454
+ expect(store['person1'].name).to eq('Joe')
455
+ expect(store['person2']).to be_nil
456
+
457
+ capture_io { store.gc }
458
+ capture_io { expect { store.check }.to_not raise_error }
459
+ expect { store.delete_store }.to_not raise_error
393
460
  end
394
461
 
395
462
  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)
463
+ store = PEROBS::Store.new(@db_file)
464
+ store.transaction do
465
+ store['person0'] = p0 = store.new(Person)
399
466
  p0.name = 'Jimmy'
400
- @store.transaction do
401
- @store['person1'] = p1 = @store.new(Person)
467
+ store.transaction do
468
+ store['person1'] = p1 = store.new(Person)
402
469
  p1.name = 'Joe'
403
470
  begin
404
- @store.transaction do
405
- @store['person2'] = p2 = @store.new(Person)
471
+ store.transaction do
472
+ store['person2'] = p2 = store.new(Person)
406
473
  p2.name = 'Jane'
407
474
  raise POSError
408
475
  end
@@ -411,55 +478,67 @@ describe PEROBS::Store do
411
478
  p1.name = 'Jane'
412
479
  end
413
480
  end
414
- expect(@store['person0'].name).to eq('Jimmy')
415
- expect(@store['person1'].name).to eq('Jane')
416
- expect(@store['person2']).to be_nil
481
+ expect(store['person0'].name).to eq('Jimmy')
482
+ expect(store['person1'].name).to eq('Jane')
483
+ expect(store['person2']).to be_nil
484
+
485
+ capture_io { store.gc }
486
+ capture_io { expect { store.check }.to_not raise_error }
487
+ expect { store.delete_store }.to_not raise_error
417
488
  end
418
489
 
419
490
  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)
491
+ store = PEROBS::Store.new(@db_file)
492
+ expect(store.statistics[:in_memory_objects]).to eq(1)
493
+ store['person'] = store.new(Person)
423
494
  # We have the root hash and the Person object.
424
- expect(@store.statistics[:in_memory_objects]).to eq(2)
425
- @store.sync
495
+ expect(store.statistics[:in_memory_objects]).to eq(2)
496
+ store.sync
426
497
  GC.start
427
498
  # Now the Person should be gone from memory.
428
499
  # Ruby 2.3 and later has changed the GC so that this does not work
429
500
  # reliably anymore. The GC seems to operate lazyly.
430
- #expect(@store.statistics[:in_memory_objects]).to eq(1)
501
+ #expect(store.statistics[:in_memory_objects]).to eq(1)
502
+
503
+ capture_io { store.gc }
504
+ capture_io { expect { store.check }.to_not raise_error }
505
+ expect { store.delete_store }.to_not raise_error
431
506
  end
432
507
 
433
508
  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'])
509
+ store = PEROBS::Store.new(@db_file)
510
+ store['root'] = store.new(O0)
511
+ store.sync
512
+ capture_io { expect(store.check).to eq(0) }
513
+ capture_io { store.exit }
514
+
515
+ store = PEROBS::Store.new(@db_file)
516
+ capture_io { expect(store.check).to eq(0) }
517
+ expect(store['root'].child.parent).to eq(store['root'])
518
+
519
+ capture_io { store.gc }
520
+ capture_io { expect { store.check }.to_not raise_error }
521
+ expect { store.delete_store }.to_not raise_error
443
522
  end
444
523
 
445
524
  it 'should handle frequent updates of objects' do
446
- @store = PEROBS::Store.new(@db_file)
525
+ store = PEROBS::Store.new(@db_file)
447
526
  count = 10000
448
527
  0.upto(count) do |i|
449
528
  key = "Obj#{i}"
450
- @store[key] = p = @store.new(Person)
529
+ store[key] = p = store.new(Person)
451
530
  p.name = "0:#{i}:" + 'X' * rand(64)
452
531
  end
453
532
 
454
533
  0.upto(10) do |iteration|
455
534
  0.upto(count) do |i|
456
535
  key = "Obj#{i}"
457
- p = @store[key]
536
+ p = store[key]
458
537
  p.name = "#{iteration}:#{i}:" + 'X' * rand(64)
459
538
  end
460
539
  0.upto(count) do |i|
461
540
  key = "Obj#{i}"
462
- p = @store[key]
541
+ p = store[key]
463
542
  o_it, o_i, o_x = p.name.split(':')
464
543
  if o_it.to_i != iteration
465
544
  $stderr.puts "Mismatch of #{p._id} with value #{o_it}:#{i}"
@@ -467,37 +546,41 @@ describe PEROBS::Store do
467
546
  expect(o_it.to_i).to eql(iteration)
468
547
  expect(o_i.to_i).to eql(i)
469
548
  end
470
- @store.check
549
+ capture_io { expect(store.check).to eql(0) }
471
550
  end
551
+
552
+ capture_io { store.gc }
553
+ capture_io { expect { store.check }.to_not raise_error }
554
+ expect { store.delete_store }.to_not raise_error
472
555
  end
473
556
 
474
557
  it 'should survive a real world usage test' do
475
558
  options = { :engine => PEROBS::FlatFileDB }
476
- @store = PEROBS::Store.new(@db_file, options)
559
+ store = PEROBS::Store.new(@db_file, options)
477
560
  ref = {}
478
561
 
479
562
  deletions_since_last_gc = 0
480
- 0.upto(10000) do |i|
563
+ 0.upto(5000) do |i|
481
564
  key = "o#{i}"
482
565
  case rand(9)
483
566
  when 0
484
567
  # Add 'A' person
485
568
  value = key + 'A' * rand(512)
486
- @store[key] = p = @store.new(Person)
569
+ store[key] = p = store.new(Person)
487
570
  p.name = value
488
571
  ref[key] = value
489
572
  when 1
490
573
  # Add 'B' person
491
574
  value = key + 'B' * rand(32)
492
- @store[key] = p = @store.new(Person)
575
+ store[key] = p = store.new(Person)
493
576
  p.name = value
494
577
  ref[key] = value
495
578
  when 2
496
579
  # Delete a root entry
497
580
  if ref.keys.length > 11
498
581
  key = ref.keys[rand(ref.keys.length)]
499
- expect(@store[key]).not_to be_nil
500
- @store[key] = nil
582
+ expect(store[key]).not_to be_nil
583
+ store[key] = nil
501
584
  ref.delete(key)
502
585
  deletions_since_last_gc += 1
503
586
  end
@@ -505,34 +588,34 @@ describe PEROBS::Store do
505
588
  # Update a person entry
506
589
  if ref.keys.length > 0
507
590
  key = ref.keys[rand(ref.keys.length)]
508
- expect(@store[key]).not_to be_nil
591
+ expect(store[key]).not_to be_nil
509
592
  value = key + 'C' * rand(996)
510
- p = @store[key]
593
+ p = store[key]
511
594
  p.name = value
512
595
  ref[key] = value
513
596
  end
514
597
  when 4
515
598
  # Call garbage collector
516
599
  if rand(60) == 0
517
- @store.gc
518
- stats = @store.statistics
600
+ capture_io { store.gc }
601
+ stats = store.statistics
519
602
  expect(stats.marked_objects).to eq(ref.length)
520
603
  expect(stats.swept_objects).to eq(deletions_since_last_gc)
521
604
  deletions_since_last_gc = 0
522
- expect(@store.gc).to eq(deletions_since_last_gc)
605
+ capture_io { expect(store.gc).to eq(deletions_since_last_gc) }
523
606
  end
524
607
  when 5
525
608
  # Sync store and reload
526
609
  if rand(15) == 0
527
- @store.exit
528
- @store = PEROBS::Store.new(@db_file, options)
610
+ capture_io { store.exit }
611
+ store = PEROBS::Store.new(@db_file, options)
529
612
  end
530
613
  when 6
531
614
  # Replace an entry with 'C' person
532
615
  if ref.keys.length > 13
533
616
  key = ref.keys[(ref.keys.length / 13).to_i]
534
617
  value = key + 'D' * rand(1024)
535
- @store[key] = p = @store.new(Person)
618
+ store[key] = p = store.new(Person)
536
619
  p.name = value
537
620
  ref[key] = value
538
621
  deletions_since_last_gc += 1
@@ -540,57 +623,65 @@ describe PEROBS::Store do
540
623
  when 7
541
624
  # Sync and check store
542
625
  if rand(50) == 0
543
- #@store.sync
544
- expect(@store.check(false)).to eq(0)
626
+ #store.sync
627
+ capture_io { expect(store.check(false)).to eq(0) }
545
628
  end
546
629
  when 8
547
630
  # Compare a random entry with reference entry
548
631
  if ref.keys.length > 0
549
632
  key = ref.keys[rand(ref.keys.length)]
550
- expect(@store[key].name).to eq(ref[key])
633
+ expect(store[key].name).to eq(ref[key])
551
634
  end
552
635
  end
553
636
  #ref.each do |k, v|
554
- # expect(@store[k].name).to eq(v), "failure in mode #{i}"
637
+ # expect(store[k].name).to eq(v), "failure in mode #{i}"
555
638
  #end
556
639
  end
557
640
 
558
641
  ref.each do |k, v|
559
- expect(@store[k].name).to eq(v)
642
+ expect(store[k].name).to eq(v)
560
643
  end
644
+
645
+ capture_io { store.gc }
646
+ capture_io { expect { store.check }.to_not raise_error }
647
+ expect { store.delete_store }.to_not raise_error
561
648
  end
562
649
 
563
650
  it 'should copy the database' do
564
- @store = PEROBS::Store.new(@db_file)
565
- @store['person0'] = p0 = @store.new(Person)
651
+ store = PEROBS::Store.new(@db_file)
652
+ store['person0'] = p0 = store.new(Person)
566
653
  id0 = p0._id
567
- p1 = @store.new(Person)
654
+ p1 = store.new(Person)
568
655
  id1 = p1._id
569
- p2 = @store.new(Person)
656
+ p2 = store.new(Person)
570
657
  id2 = p2._id
571
658
  p1.related = p2
572
659
  p2.related = p1
573
660
  p0.related = p1
574
- p3 = @store.new(PEROBS::Array)
575
- @store['persons'] = p3
661
+ p3 = store.new(PEROBS::Array)
662
+ store['persons'] = p3
576
663
  p3 << p0
577
664
  p3 << p1
578
665
  p3 << p2
579
666
  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)
667
+ expect(store['person0']._id).to eq(id0)
668
+ expect(store['person0'].related._id).to eq(id1)
669
+ expect(store['person0'].related.related._id).to eq(id2)
670
+
671
+ store.copy(@db_file_new, { :engine => PEROBS::FlatFileDB })
672
+ store.delete_store
673
+
674
+ store = PEROBS::Store.new(@db_file_new, { :engine => PEROBS::FlatFileDB })
675
+ expect(store['person0']._id).to eq(id0)
676
+ expect(store['person0'].related._id).to eq(id1)
677
+ expect(store['person0'].related.related._id).to eq(id2)
678
+ expect(store['persons'][0]).to eq(store['person0'])
679
+ expect(store['persons'][1]).to eq(store['person0'].related)
680
+ expect(store['persons'][2]).to eq(store['person0'].related.related)
681
+
682
+ capture_io { store.gc }
683
+ capture_io { expect { store.check }.to_not raise_error }
684
+ expect { store.delete_store }.to_not raise_error
594
685
  end
595
686
 
596
687
  end