perobs 3.0.1 → 4.3.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 (75) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +19 -18
  3. data/lib/perobs.rb +2 -0
  4. data/lib/perobs/Array.rb +68 -21
  5. data/lib/perobs/BTree.rb +110 -54
  6. data/lib/perobs/BTreeBlob.rb +14 -13
  7. data/lib/perobs/BTreeDB.rb +11 -10
  8. data/lib/perobs/BTreeNode.rb +551 -197
  9. data/lib/perobs/BTreeNodeCache.rb +10 -8
  10. data/lib/perobs/BTreeNodeLink.rb +11 -1
  11. data/lib/perobs/BigArray.rb +285 -0
  12. data/lib/perobs/BigArrayNode.rb +1002 -0
  13. data/lib/perobs/BigHash.rb +246 -0
  14. data/lib/perobs/BigTree.rb +197 -0
  15. data/lib/perobs/BigTreeNode.rb +873 -0
  16. data/lib/perobs/Cache.rb +47 -22
  17. data/lib/perobs/ClassMap.rb +2 -2
  18. data/lib/perobs/ConsoleProgressMeter.rb +61 -0
  19. data/lib/perobs/DataBase.rb +4 -3
  20. data/lib/perobs/DynamoDB.rb +62 -20
  21. data/lib/perobs/EquiBlobsFile.rb +174 -59
  22. data/lib/perobs/FNV_Hash_1a_64.rb +54 -0
  23. data/lib/perobs/FlatFile.rb +536 -242
  24. data/lib/perobs/FlatFileBlobHeader.rb +120 -84
  25. data/lib/perobs/FlatFileDB.rb +58 -27
  26. data/lib/perobs/FuzzyStringMatcher.rb +175 -0
  27. data/lib/perobs/Hash.rb +129 -35
  28. data/lib/perobs/IDList.rb +144 -0
  29. data/lib/perobs/IDListPage.rb +107 -0
  30. data/lib/perobs/IDListPageFile.rb +180 -0
  31. data/lib/perobs/IDListPageRecord.rb +142 -0
  32. data/lib/perobs/LockFile.rb +3 -0
  33. data/lib/perobs/Object.rb +28 -20
  34. data/lib/perobs/ObjectBase.rb +53 -10
  35. data/lib/perobs/PersistentObjectCache.rb +142 -0
  36. data/lib/perobs/PersistentObjectCacheLine.rb +99 -0
  37. data/lib/perobs/ProgressMeter.rb +97 -0
  38. data/lib/perobs/SpaceManager.rb +273 -0
  39. data/lib/perobs/SpaceTree.rb +63 -47
  40. data/lib/perobs/SpaceTreeNode.rb +134 -115
  41. data/lib/perobs/SpaceTreeNodeLink.rb +1 -1
  42. data/lib/perobs/StackFile.rb +1 -1
  43. data/lib/perobs/Store.rb +180 -70
  44. data/lib/perobs/version.rb +1 -1
  45. data/perobs.gemspec +4 -4
  46. data/test/Array_spec.rb +48 -39
  47. data/test/BTreeDB_spec.rb +2 -2
  48. data/test/BTree_spec.rb +50 -1
  49. data/test/BigArray_spec.rb +261 -0
  50. data/test/BigHash_spec.rb +152 -0
  51. data/test/BigTreeNode_spec.rb +153 -0
  52. data/test/BigTree_spec.rb +259 -0
  53. data/test/EquiBlobsFile_spec.rb +105 -5
  54. data/test/FNV_Hash_1a_64_spec.rb +59 -0
  55. data/test/FlatFileDB_spec.rb +199 -15
  56. data/test/FuzzyStringMatcher_spec.rb +261 -0
  57. data/test/Hash_spec.rb +27 -16
  58. data/test/IDList_spec.rb +77 -0
  59. data/test/LegacyDBs/LegacyDB.rb +155 -0
  60. data/test/LegacyDBs/version_3/class_map.json +1 -0
  61. data/test/LegacyDBs/version_3/config.json +1 -0
  62. data/test/LegacyDBs/version_3/database.blobs +0 -0
  63. data/test/LegacyDBs/version_3/database_spaces.blobs +0 -0
  64. data/test/LegacyDBs/version_3/index.blobs +0 -0
  65. data/test/LegacyDBs/version_3/version +1 -0
  66. data/test/LockFile_spec.rb +9 -6
  67. data/test/Object_spec.rb +5 -5
  68. data/test/SpaceManager_spec.rb +176 -0
  69. data/test/SpaceTree_spec.rb +27 -9
  70. data/test/Store_spec.rb +353 -206
  71. data/test/perobs_spec.rb +7 -3
  72. data/test/spec_helper.rb +9 -4
  73. metadata +59 -16
  74. data/lib/perobs/SpaceTreeNodeCache.rb +0 -76
  75. data/lib/perobs/TreeDB.rb +0 -277
@@ -0,0 +1 @@
1
+ {"PEROBS::Hash":0,"PEROBS::Array":1,"LegacyDB::Fragment":2}
@@ -0,0 +1 @@
1
+ {"serializer":{"json_class":"Symbol","s":"json"}}
@@ -0,0 +1 @@
1
+ 3
@@ -30,7 +30,11 @@ require 'perobs/LockFile'
30
30
  describe PEROBS::LockFile do
31
31
 
32
32
  before(:each) do
33
- @dir = Dir.mktmpdir('LockFile')
33
+ PEROBS.log.open($stderr)
34
+ PEROBS.log.level = Logger::INFO
35
+ @dir = File.join(Dir.tmpdir,
36
+ "#{File.basename('LockFile_spec')}.#{rand(2**32)}")
37
+ FileUtils.mkdir_p(@dir)
34
38
  @file = File.join(@dir, 'LockFile.lock')
35
39
  end
36
40
 
@@ -42,7 +46,6 @@ describe PEROBS::LockFile do
42
46
  capture_io do
43
47
  expect(PEROBS::LockFile.new('/foo/bar/foobar').lock).to be false
44
48
  end
45
- PEROBS.log.open($stderr)
46
49
  end
47
50
 
48
51
  it 'should support taking and releasing the lock' do
@@ -59,7 +62,7 @@ describe PEROBS::LockFile do
59
62
  expect(lock.is_locked?).to be true
60
63
  lock.forced_unlock
61
64
  expect(lock.is_locked?).to be false
62
- out = capture_io{ expect(lock.unlock).to be false }
65
+ out = capture_io{ expect(lock.unlock).to be false }.log
63
66
  expect(out).to include('There is no current lock to release')
64
67
  end
65
68
 
@@ -67,7 +70,7 @@ describe PEROBS::LockFile do
67
70
  lock1 = PEROBS::LockFile.new(@file)
68
71
  expect(lock1.lock).to be true
69
72
  lock2 = PEROBS::LockFile.new(@file)
70
- out = capture_io { expect(lock2.lock).to be false }
73
+ out = capture_io { expect(lock2.lock).to be false }.log
71
74
  expect(out).to include('due to timeout')
72
75
  expect(lock1.unlock).to be true
73
76
  expect(lock2.lock).to be true
@@ -105,7 +108,7 @@ describe PEROBS::LockFile do
105
108
  end
106
109
  lock2 = PEROBS::LockFile.new(@file,
107
110
  { :max_retries => 2, :pause_secs => 0.5 })
108
- out = capture_io { expect(lock2.lock).to be false }
111
+ out = capture_io { expect(lock2.lock).to be false }.log
109
112
  expect(out).to include('due to timeout')
110
113
  Process.wait(pid)
111
114
  end
@@ -123,7 +126,7 @@ describe PEROBS::LockFile do
123
126
  end
124
127
 
125
128
  lock2 = PEROBS::LockFile.new(@file, { :timeout_secs => 1 })
126
- out = capture_io { expect(lock2.lock).to be true }
129
+ out = capture_io { expect(lock2.lock).to be true }.log
127
130
  expect(out).to include('Old lock file found for PID')
128
131
  expect(lock2.unlock).to be true
129
132
  Process.wait(pid)
data/test/Object_spec.rb CHANGED
@@ -28,7 +28,7 @@ require 'perobs'
28
28
 
29
29
  class O1_Object < PEROBS::Object
30
30
 
31
- po_attr :a1
31
+ attr_persist :a1
32
32
 
33
33
  def initialize(store)
34
34
  super
@@ -38,7 +38,7 @@ end
38
38
 
39
39
  class O2_Object < PEROBS::Object
40
40
 
41
- po_attr :a1, :a2, :a3, :a4
41
+ attr_persist :a1, :a2, :a3, :a4
42
42
 
43
43
  def initialize(store)
44
44
  super
@@ -99,7 +99,7 @@ describe PEROBS::Store do
99
99
  expect(o2.a1).to be_nil
100
100
  expect(o2.a3).to eq(o1)
101
101
  expect(o2.a4).to eq(42)
102
- @store.sync
102
+ @store.exit
103
103
  end
104
104
 
105
105
  it 'should persist assigned values' do
@@ -114,7 +114,7 @@ describe PEROBS::Store do
114
114
  @store['o3'] = o3 = @store.new(O1_Object)
115
115
  o3.a1 = @store.new(PEROBS::Array)
116
116
  end
117
- @store.sync
117
+ @store.exit
118
118
  @store = nil
119
119
  GC.start
120
120
 
@@ -163,7 +163,7 @@ describe PEROBS::Store do
163
163
  it 'should raise an error when no attributes are defined' do
164
164
  @store['o3'] = @store.new(O3)
165
165
  PEROBS.log.open(StringIO.new)
166
- expect { @store.sync }.to raise_error(PEROBS::FatalError)
166
+ expect { @store.exit }.to raise_error(PEROBS::FatalError)
167
167
  PEROBS.log.open($stderr)
168
168
  end
169
169
 
@@ -0,0 +1,176 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright (c) 2020 by Chris Schlaeger <chris@taskjuggler.org>
4
+ #
5
+ # MIT License
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining
8
+ # a copy of this software and associated documentation files (the
9
+ # "Software"), to deal in the Software without restriction, including
10
+ # without limitation the rights to use, copy, modify, merge, publish,
11
+ # distribute, sublicense, and/or sell copies of the Software, and to
12
+ # permit persons to whom the Software is furnished to do so, subject to
13
+ # the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be
16
+ # included in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+
26
+ require 'fileutils'
27
+
28
+ require 'spec_helper'
29
+ require 'perobs/SpaceManager'
30
+ require 'perobs/ProgressMeter'
31
+
32
+ describe PEROBS::SpaceManager do
33
+
34
+ before(:all) do
35
+ @db_dir = generate_db_name('SpaceManager')
36
+ FileUtils.mkdir_p(@db_dir)
37
+ @m = PEROBS::SpaceManager.new(@db_dir, PEROBS::ProgressMeter.new, 5)
38
+ PEROBS.log.level = Logger::ERROR
39
+ PEROBS.log.open($stderr)
40
+ end
41
+
42
+ after(:all) do
43
+ FileUtils.rm_rf(@db_dir)
44
+ end
45
+
46
+ it 'should open the space manager' do
47
+ @m.open
48
+ expect(@m.to_a).to eql([])
49
+ end
50
+
51
+ it 'should support adding spaces' do
52
+ @m.add_space(80, 8)
53
+ expect(@m.has_space?(80, 8)).to be true
54
+ expect(@m.to_a).to eql([[80, 8]])
55
+ expect(@m.check).to be true
56
+ @m.add_space(40, 4)
57
+ expect(@m.has_space?(40, 4)).to be true
58
+ expect(@m.to_a).to eql([[40, 4], [80, 8]])
59
+ @m.add_space(20, 2)
60
+ expect(@m.has_space?(20, 2)).to be true
61
+ expect(@m.to_a).to eql([[20, 2], [40, 4], [80, 8]])
62
+ @m.add_space(160, 16)
63
+ expect(@m.has_space?(160, 16)).to be true
64
+ expect(@m.to_a).to eql([[20, 2], [40, 4], [80, 8], [160, 16]])
65
+ @m.add_space(320, 32)
66
+ expect(@m.has_space?(320, 32)).to be true
67
+ expect(@m.to_a).to eql([[20, 2], [40, 4], [80, 8], [160, 16], [320, 32]])
68
+ @m.add_space(100, 10)
69
+ expect(@m.has_space?(100, 10)).to be true
70
+ expect(@m.to_a).to eql([[20, 2], [40, 4], [80, 8], [100, 10], [160, 16], [320, 32]])
71
+ @m.add_space(81, 8)
72
+ expect(@m.has_space?(81, 8)).to be true
73
+ expect(@m.to_a).to eql([[20, 2], [40, 4], [80, 8], [81, 8], [100, 10], [160, 16], [320, 32]])
74
+ expect(@m.check).to be true
75
+ end
76
+
77
+ it 'should keep values over an close/open' do
78
+ @m.add_space(1, 15)
79
+ expect(@m.check).to be true
80
+ @m.close
81
+ @m.open
82
+ expect(@m.check).to be true
83
+ expect(@m.to_a).to eql([[1, 15], [20, 2], [40, 4], [80, 8], [81, 8], [100, 10], [160, 16], [320, 32]])
84
+ end
85
+
86
+ it 'should support clearing all spaces' do
87
+ @m.clear
88
+ expect(@m.to_a).to eql([])
89
+ @m.add_space(1, 1)
90
+ @m.add_space(2, 2)
91
+ @m.clear
92
+ expect(@m.to_a).to eql([])
93
+ end
94
+
95
+ it 'should return exactly matching spaces' do
96
+ @m.clear
97
+ add_sizes([ 10, 5, 15, 10 ])
98
+ expect(@m.to_a).to eql([[0, 10], [1, 5], [2, 15], [3, 10]])
99
+ expect(@m.get_space(10)).to eql([3, 10])
100
+ expect(@m.get_space(10)).to eql([0, 10])
101
+ expect(@m.to_a).to eql([[1, 5], [2, 15]])
102
+ expect(@m.added_spaces).to eql(4)
103
+ expect(@m.recycled_spaces).to eql(2)
104
+ expect(@m.failed_requests).to eql(0)
105
+
106
+ @m.clear
107
+ add_sizes([ 10, 5, 15, 10, 10 ])
108
+ expect(@m.to_a).to eql([[0, 10], [1, 5], [2, 15], [3, 10], [4, 10]])
109
+ expect(@m.get_space(10)).to eql([4, 10])
110
+ expect(@m.get_space(10)).to eql([3, 10])
111
+ expect(@m.get_space(10)).to eql([0, 10])
112
+ expect(@m.to_a).to eql([[1, 5], [2, 15]])
113
+ expect(@m.added_spaces).to eql(5)
114
+ expect(@m.recycled_spaces).to eql(3)
115
+ expect(@m.failed_requests).to eql(0)
116
+ end
117
+
118
+ it "should return nil if no space can be found" do
119
+ expect(@m.get_space(42)).to be nil
120
+ expect(@m.get_space(9)).to be nil
121
+ expect(@m.get_space(11)).to be nil
122
+ expect(@m.recycled_spaces).to eql(3)
123
+ expect(@m.failed_requests).to eql(3)
124
+ end
125
+
126
+ it 'should support a real-world traffic pattern' do
127
+ address = 0
128
+ spaces = []
129
+ @m.clear
130
+ 0.upto(1500) do
131
+ case rand(4)
132
+ when 0
133
+ # Insert new values
134
+ rand(9).times do
135
+ size = 20 + rand(80)
136
+ @m.add_space(address, size)
137
+ spaces << [ address, size ]
138
+ address += size
139
+ end
140
+ when 1
141
+ # Remove some values
142
+ rand(7).times do
143
+ size = rand(110)
144
+ if (space = @m.get_space(size))
145
+ expect(spaces.include?(space)).to be true
146
+ spaces.delete(space)
147
+ end
148
+ end
149
+ when 2
150
+ if rand(10) == 0
151
+ expect(@m.check).to be true
152
+ spaces.each do |address, size|
153
+ expect(@m.has_space?(address, size)).to be true
154
+ end
155
+ @m.to_a.each do |address, size|
156
+ expect(spaces.include?([ address, size ])).to be true
157
+ end
158
+ end
159
+ when 3
160
+ if rand(200) == 0
161
+ expect(@m.check).to be true
162
+ @m.close
163
+ @m.open
164
+ expect(@m.check).to be true
165
+ end
166
+ end
167
+ end
168
+ end
169
+
170
+ def add_sizes(sizes)
171
+ sizes.each_with_index do |size, i|
172
+ @m.add_space(i, size)
173
+ end
174
+ end
175
+
176
+ end
@@ -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
@@ -94,12 +97,12 @@ EOT
94
97
  end
95
98
 
96
99
  it 'should find the smallest node' do
97
- node = @m.instance_variable_get('@root').find_smallest_node
100
+ node = @m.root.find_smallest_node
98
101
  expect(node.size).to eql(2)
99
102
  end
100
103
 
101
104
  it 'should find the largest node' do
102
- node = @m.instance_variable_get('@root').find_largest_node
105
+ node = @m.root.find_largest_node
103
106
  expect(node.size).to eql(32)
104
107
  expect(node.node_address).to eql(5)
105
108
  expect(node.parent.size).to eql(16)
@@ -119,12 +122,14 @@ EOT
119
122
  add_sizes([ 10, 5, 15, 10 ])
120
123
  expect(@m.to_a).to eql([[0, 10], [1, 5], [3, 10], [2, 15]])
121
124
  expect(@m.get_space(10)).to eql([0, 10])
125
+ expect(@m.to_a).to eql([[3, 10], [1, 5], [2, 15]])
122
126
 
123
127
  @m.clear
124
128
  add_sizes([ 10, 5, 15, 10, 10 ])
125
129
  expect(@m.to_a).to eql([[0, 10], [1, 5], [4, 10], [3, 10], [2, 15]])
126
130
  expect(@m.get_space(10)).to eql([0, 10])
127
131
  expect(@m.get_space(10)).to eql([4, 10])
132
+ expect(@m.to_a).to eql([[3, 10], [1, 5], [2, 15]])
128
133
  end
129
134
 
130
135
  it 'should delete a smaller node' do
@@ -208,14 +213,15 @@ EOT
208
213
  it 'should support a real-world traffic pattern' do
209
214
  address = 0
210
215
  spaces = []
211
- 0.upto(500) do
212
- case rand(3)
216
+ @m.clear
217
+ 0.upto(1000) do
218
+ case rand(4)
213
219
  when 0
214
220
  # Insert new values
215
221
  rand(9).times do
216
222
  size = 20 + rand(5000)
217
223
  @m.add_space(address, size)
218
- spaces << [address, size]
224
+ spaces << [ address, size ]
219
225
  address += size
220
226
  end
221
227
  when 1
@@ -228,9 +234,21 @@ EOT
228
234
  end
229
235
  end
230
236
  when 2
231
- expect(@m.check).to be true
232
- spaces.each do |address, size|
233
- expect(@m.has_space?(address, size)).to be true
237
+ if rand(10) == 0
238
+ expect(@m.check).to be true
239
+ spaces.each do |address, size|
240
+ expect(@m.has_space?(address, size)).to be true
241
+ end
242
+ @m.each do |address, size|
243
+ expect(spaces.include?([ address, size ])).to be true
244
+ end
245
+ end
246
+ when 3
247
+ if rand(100) == 0
248
+ expect(@m.check).to be true
249
+ @m.close
250
+ @m.open
251
+ expect(@m.check).to be true
234
252
  end
235
253
  end
236
254
  end
data/test/Store_spec.rb CHANGED
@@ -31,9 +31,9 @@ end
31
31
 
32
32
  class Person < PEROBS::Object
33
33
 
34
- po_attr :name, :zip, :bmi, :married, :related, :relatives
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)
@@ -44,9 +44,9 @@ end
44
44
 
45
45
  class PersonN < PEROBS::Object
46
46
 
47
- po_attr :name, :zip, :bmi, :married, :related, :relatives
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)
@@ -57,17 +57,17 @@ end
57
57
 
58
58
  class O0 < PEROBS::Object
59
59
 
60
- po_attr :child
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
68
68
  class O1 < PEROBS::Object
69
69
 
70
- po_attr :parent
70
+ attr_persist :parent
71
71
 
72
72
  def initialize(store, p = nil)
73
73
  super(store)
@@ -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.sync
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,164 +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.sync
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.sync
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 = PEROBS::Store.new(@db_file)
222
- expect(@store.object_by_id(id1)).to be_nil
223
- expect(@store['person2']._id).to eq(id2)
224
- 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
225
271
  end
226
272
 
227
273
  it 'should handle cyclicly linked objects' do
228
- @store = PEROBS::Store.new(@db_file)
229
- @store['person0'] = p0 = @store.new(Person)
274
+ store = PEROBS::Store.new(@db_file)
275
+ store['person0'] = p0 = store.new(Person)
230
276
  id0 = p0._id
231
- p1 = @store.new(Person)
277
+ p1 = store.new(Person)
232
278
  id1 = p1._id
233
- p2 = @store.new(Person)
279
+ p2 = store.new(Person)
234
280
  id2 = p2._id
235
281
  p1.related = p2
236
282
  p2.related = p1
237
283
  p0.related = p1
238
- expect(@store.check).to eq(0)
239
- expect(@store.gc).to eq(0)
284
+ capture_io { expect(store.check).to eq(0) }
285
+ capture_io { expect(store.gc).to eq(0) }
240
286
  p0 = p1 = p2 = nil
241
- GC.start
242
- @store = PEROBS::Store.new(@db_file)
243
- expect(@store['person0']._id).to eq(id0)
244
- expect(@store['person0'].related._id).to eq(id1)
245
- expect(@store['person0'].related.related._id).to eq(id2)
246
-
247
- @store['person0'].related = nil
248
- expect(@store.gc).to eq(2)
249
- GC.start
250
- expect(@store.object_by_id(id1)).to be_nil
251
- expect(@store.object_by_id(id2)).to be_nil
252
-
253
- @store = PEROBS::Store.new(@db_file)
254
- expect(@store.check).to eq(0)
255
- expect(@store.object_by_id(id1)).to be_nil
256
- 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
257
307
  end
258
308
 
259
309
  it 'should support a successful transaction' do
260
- @store = PEROBS::Store.new(@db_file)
261
- @store.transaction do
262
- @store['person0'] = p0 = @store.new(Person)
310
+ store = PEROBS::Store.new(@db_file)
311
+ store.transaction do
312
+ store['person0'] = p0 = store.new(Person)
263
313
  p0.name = 'Jimmy'
264
314
  end
265
- 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
266
320
  end
267
321
 
268
322
  it 'should handle a failed transaction 1' do
269
- @store = PEROBS::Store.new(@db_file)
323
+ store = PEROBS::Store.new(@db_file)
270
324
  begin
271
- @store.transaction do
272
- @store['person0'] = p0 = @store.new(Person)
325
+ store.transaction do
326
+ store['person0'] = p0 = store.new(Person)
273
327
  p0.name = 'Jimmy'
274
328
  raise POSError
275
329
  end
276
330
  rescue POSError
277
331
  end
278
- 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
279
337
  end
280
338
 
281
339
  it 'should handle a failed transaction 2' do
282
- @store = PEROBS::Store.new(@db_file)
283
- @store['person1'] = p1 = @store.new(Person)
340
+ store = PEROBS::Store.new(@db_file)
341
+ store['person1'] = p1 = store.new(Person)
284
342
  p1.name = 'Joe'
285
343
  begin
286
- @store.transaction do
287
- @store['person0'] = p0 = @store.new(Person)
344
+ store.transaction do
345
+ store['person0'] = p0 = store.new(Person)
288
346
  p0.name = 'Jimmy'
289
347
  raise POSError
290
348
  end
291
349
  rescue POSError
292
350
  end
293
- expect(@store['person1'].name).to eq('Joe')
294
- 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
295
357
  end
296
358
 
297
359
  it 'should support a successful nested transaction' do
298
- @store = PEROBS::Store.new(@db_file)
299
- @store.transaction do
300
- @store['person0'] = p0 = @store.new(Person)
360
+ store = PEROBS::Store.new(@db_file)
361
+ store.transaction do
362
+ store['person0'] = p0 = store.new(Person)
301
363
  p0.name = 'Jimmy'
302
- @store.transaction do
303
- @store['person1'] = p1 = @store.new(Person)
364
+ store.transaction do
365
+ store['person1'] = p1 = store.new(Person)
304
366
  p1.name = 'Joe'
305
367
  end
306
368
  end
307
- expect(@store['person0'].name).to eq('Jimmy')
308
- 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
309
375
  end
310
376
 
311
377
  it 'should handle a failed nested transaction 1' do
312
- @store = PEROBS::Store.new(@db_file)
378
+ store = PEROBS::Store.new(@db_file)
313
379
  begin
314
- @store.transaction do
315
- @store['person0'] = p0 = @store.new(Person)
380
+ store.transaction do
381
+ store['person0'] = p0 = store.new(Person)
316
382
  p0.name = 'Jimmy'
317
383
  begin
318
- @store.transaction do
319
- @store['person1'] = p1 = @store.new(Person)
384
+ store.transaction do
385
+ store['person1'] = p1 = store.new(Person)
320
386
  p1.name = 'Joe'
321
387
  raise POSError
322
388
  end
@@ -325,58 +391,70 @@ describe PEROBS::Store do
325
391
  end
326
392
  rescue POSError
327
393
  end
328
- expect(@store['person0'].name).to eq('Jimmy')
329
- 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
330
400
  end
331
401
 
332
402
  it 'should handle a failed nested transaction 2' do
333
- @store = PEROBS::Store.new(@db_file)
403
+ store = PEROBS::Store.new(@db_file)
334
404
  begin
335
- @store.transaction do
336
- @store['person0'] = p0 = @store.new(Person)
405
+ store.transaction do
406
+ store['person0'] = p0 = store.new(Person)
337
407
  p0.name = 'Jimmy'
338
- @store.transaction do
339
- @store['person1'] = p1 = @store.new(Person)
408
+ store.transaction do
409
+ store['person1'] = p1 = store.new(Person)
340
410
  p1.name = 'Joe'
341
411
  end
342
412
  raise POSError
343
413
  end
344
414
  rescue POSError
345
415
  end
346
- expect(@store['person0']).to be_nil
347
- 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
348
422
  end
349
423
 
350
424
  it 'should support a successful 2-level nested transaction' do
351
- @store = PEROBS::Store.new(@db_file)
352
- @store.transaction do
353
- @store['person0'] = p0 = @store.new(Person)
425
+ store = PEROBS::Store.new(@db_file)
426
+ store.transaction do
427
+ store['person0'] = p0 = store.new(Person)
354
428
  p0.name = 'Jimmy'
355
- @store.transaction do
356
- @store['person1'] = p1 = @store.new(Person)
429
+ store.transaction do
430
+ store['person1'] = p1 = store.new(Person)
357
431
  p1.name = 'Joe'
358
- @store.transaction do
359
- @store['person2'] = p2 = @store.new(Person)
432
+ store.transaction do
433
+ store['person2'] = p2 = store.new(Person)
360
434
  p2.name = 'Jane'
361
435
  end
362
436
  end
363
437
  end
364
- expect(@store['person0'].name).to eq('Jimmy')
365
- expect(@store['person1'].name).to eq('Joe')
366
- 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
367
445
  end
368
446
 
369
447
  it 'should handle a failed 2-level nested transaction 1' do
370
- @store = PEROBS::Store.new(@db_file)
371
- @store.transaction do
372
- @store['person0'] = p0 = @store.new(Person)
448
+ store = PEROBS::Store.new(@db_file)
449
+ store.transaction do
450
+ store['person0'] = p0 = store.new(Person)
373
451
  p0.name = 'Jimmy'
374
- @store.transaction do
375
- @store['person1'] = p1 = @store.new(Person)
452
+ store.transaction do
453
+ store['person1'] = p1 = store.new(Person)
376
454
  p1.name = 'Joe'
377
455
  begin
378
- @store.transaction do
379
- @store['person2'] = p2 = @store.new(Person)
456
+ store.transaction do
457
+ store['person2'] = p2 = store.new(Person)
380
458
  p2.name = 'Jane'
381
459
  raise POSError
382
460
  end
@@ -384,22 +462,26 @@ describe PEROBS::Store do
384
462
  end
385
463
  end
386
464
  end
387
- expect(@store['person0'].name).to eq('Jimmy')
388
- expect(@store['person1'].name).to eq('Joe')
389
- 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
390
472
  end
391
473
 
392
474
  it 'should handle a failed 2-level nested transaction 2' do
393
- @store = PEROBS::Store.new(@db_file)
394
- @store.transaction do
395
- @store['person0'] = p0 = @store.new(Person)
475
+ store = PEROBS::Store.new(@db_file)
476
+ store.transaction do
477
+ store['person0'] = p0 = store.new(Person)
396
478
  p0.name = 'Jimmy'
397
- @store.transaction do
398
- @store['person1'] = p1 = @store.new(Person)
479
+ store.transaction do
480
+ store['person1'] = p1 = store.new(Person)
399
481
  p1.name = 'Joe'
400
482
  begin
401
- @store.transaction do
402
- @store['person2'] = p2 = @store.new(Person)
483
+ store.transaction do
484
+ store['person2'] = p2 = store.new(Person)
403
485
  p2.name = 'Jane'
404
486
  raise POSError
405
487
  end
@@ -408,144 +490,209 @@ describe PEROBS::Store do
408
490
  p1.name = 'Jane'
409
491
  end
410
492
  end
411
- expect(@store['person0'].name).to eq('Jimmy')
412
- expect(@store['person1'].name).to eq('Jane')
413
- 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
414
500
  end
415
501
 
416
502
  it 'should track in-memory objects properly' do
417
- @store = PEROBS::Store.new(@db_file)
418
- expect(@store.statistics[:in_memory_objects]).to eq(1)
419
- @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)
420
506
  # We have the root hash and the Person object.
421
- expect(@store.statistics[:in_memory_objects]).to eq(2)
422
- @store.sync
423
- GC.start
507
+ expect(store.statistics[:in_memory_objects]).to eq(2)
508
+ store.sync
424
509
  # Now the Person should be gone from memory.
425
- expect(@store.statistics[:in_memory_objects]).to eq(1)
510
+ # Ruby 2.3 and later has changed the GC so that this does not work
511
+ # reliably anymore. The GC seems to operate lazyly.
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
426
517
  end
427
518
 
428
519
  it 'should handle nested constructors' do
429
- @store = PEROBS::Store.new(@db_file)
430
- @store['root'] = @store.new(O0)
431
- @store.sync
432
- expect(@store.check).to eq(0)
433
-
434
- @store = PEROBS::Store.new(@db_file)
435
- expect(@store.check).to eq(0)
436
- 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
533
+ end
534
+
535
+ it 'should handle frequent updates of objects' do
536
+ store = PEROBS::Store.new(@db_file)
537
+ count = 10000
538
+ 0.upto(count) do |i|
539
+ key = "Obj#{i}"
540
+ store[key] = p = store.new(Person)
541
+ p.name = "0:#{i}:" + 'X' * rand(64)
542
+ end
543
+
544
+ 0.upto(10) do |iteration|
545
+ 0.upto(count) do |i|
546
+ key = "Obj#{i}"
547
+ p = store[key]
548
+ p.name = "#{iteration}:#{i}:" + 'X' * rand(64)
549
+ end
550
+ 0.upto(count) do |i|
551
+ key = "Obj#{i}"
552
+ p = store[key]
553
+ o_it, o_i, o_x = p.name.split(':')
554
+ if o_it.to_i != iteration
555
+ $stderr.puts "Mismatch of #{p._id} with value #{o_it}:#{i}"
556
+ end
557
+ expect(o_it.to_i).to eql(iteration)
558
+ expect(o_i.to_i).to eql(i)
559
+ end
560
+ capture_io { expect(store.check).to eql(0) }
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
437
566
  end
438
567
 
439
568
  it 'should survive a real world usage test' do
440
569
  options = { :engine => PEROBS::FlatFileDB }
441
- @store = PEROBS::Store.new(@db_file, options)
570
+ store = PEROBS::Store.new(@db_file, options)
442
571
  ref = {}
443
572
 
444
573
  deletions_since_last_gc = 0
445
- 0.upto(15000) do |i|
574
+ 0.upto(5000) do |i|
446
575
  key = "o#{i}"
447
- case rand(8)
576
+ case rand(9)
448
577
  when 0
449
578
  # Add 'A' person
450
- value = 'A' * rand(512)
451
- @store[key] = p = @store.new(Person)
579
+ value = key + 'A' * rand(512)
580
+ store[key] = p = store.new(Person)
452
581
  p.name = value
453
582
  ref[key] = value
454
583
  when 1
455
584
  # Add 'B' person
456
- value = 'B' * rand(128)
457
- @store[key] = p = @store.new(Person)
585
+ value = key + 'B' * rand(32)
586
+ store[key] = p = store.new(Person)
458
587
  p.name = value
459
588
  ref[key] = value
460
589
  when 2
461
590
  # Delete a root entry
462
591
  if ref.keys.length > 11
463
- key = ref.keys[(ref.keys.length / 11).to_i]
464
- expect(@store[key]).not_to be_nil
465
- @store[key] = nil
592
+ key = ref.keys[rand(ref.keys.length)]
593
+ expect(store[key]).not_to be_nil
594
+ store[key] = nil
466
595
  ref.delete(key)
467
596
  deletions_since_last_gc += 1
468
597
  end
469
598
  when 3
599
+ # Update a person entry
600
+ if ref.keys.length > 0
601
+ key = ref.keys[rand(ref.keys.length)]
602
+ expect(store[key]).not_to be_nil
603
+ value = key + 'C' * rand(996)
604
+ p = store[key]
605
+ p.name = value
606
+ ref[key] = value
607
+ end
608
+ when 4
470
609
  # Call garbage collector
471
610
  if rand(60) == 0
472
- @store.gc
473
- stats = @store.statistics
611
+ capture_io { store.gc }
612
+ stats = store.statistics
474
613
  expect(stats.marked_objects).to eq(ref.length)
475
614
  expect(stats.swept_objects).to eq(deletions_since_last_gc)
476
615
  deletions_since_last_gc = 0
477
- expect(@store.gc).to eq(deletions_since_last_gc)
616
+ capture_io { expect(store.gc).to eq(deletions_since_last_gc) }
478
617
  end
479
- when 4
618
+ when 5
480
619
  # Sync store and reload
481
620
  if rand(15) == 0
482
- @store.exit
483
- @store = PEROBS::Store.new(@db_file, options)
621
+ capture_io { store.exit }
622
+ store = PEROBS::Store.new(@db_file, options)
484
623
  end
485
- when 5
624
+ when 6
486
625
  # Replace an entry with 'C' person
487
626
  if ref.keys.length > 13
488
627
  key = ref.keys[(ref.keys.length / 13).to_i]
489
- value = 'C' * rand(1024)
490
- @store[key] = p = @store.new(Person)
628
+ value = key + 'D' * rand(1024)
629
+ store[key] = p = store.new(Person)
491
630
  p.name = value
492
631
  ref[key] = value
493
632
  deletions_since_last_gc += 1
494
633
  end
495
- when 6
634
+ when 7
496
635
  # Sync and check store
497
636
  if rand(50) == 0
498
- @store.sync
499
- expect(@store.check(false)).to eq(0)
637
+ #store.sync
638
+ capture_io { expect(store.check(false)).to eq(0) }
500
639
  end
501
- when 7
640
+ when 8
502
641
  # Compare a random entry with reference entry
503
642
  if ref.keys.length > 0
504
- key = ref.keys[rand(ref.keys.length - 1)]
505
- expect(@store[key].name).to eq(ref[key])
643
+ key = ref.keys[rand(ref.keys.length)]
644
+ expect(store[key].name).to eq(ref[key])
506
645
  end
507
646
  end
508
647
  #ref.each do |k, v|
509
- # expect(@store[k].name).to eq(v), "failure in mode #{i}"
648
+ # expect(store[k].name).to eq(v), "failure in mode #{i}"
510
649
  #end
511
650
  end
512
651
 
513
652
  ref.each do |k, v|
514
- expect(@store[k].name).to eq(v)
653
+ expect(store[k].name).to eq(v)
515
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
516
659
  end
517
660
 
518
661
  it 'should copy the database' do
519
- @store = PEROBS::Store.new(@db_file)
520
- @store['person0'] = p0 = @store.new(Person)
662
+ store = PEROBS::Store.new(@db_file)
663
+ store['person0'] = p0 = store.new(Person)
521
664
  id0 = p0._id
522
- p1 = @store.new(Person)
665
+ p1 = store.new(Person)
523
666
  id1 = p1._id
524
- p2 = @store.new(Person)
667
+ p2 = store.new(Person)
525
668
  id2 = p2._id
526
669
  p1.related = p2
527
670
  p2.related = p1
528
671
  p0.related = p1
529
- p3 = @store.new(PEROBS::Array)
530
- @store['persons'] = p3
672
+ p3 = store.new(PEROBS::Array)
673
+ store['persons'] = p3
531
674
  p3 << p0
532
675
  p3 << p1
533
676
  p3 << p2
534
677
  p0 = p1 = p2 = p3 = nil
535
- expect(@store['person0']._id).to eq(id0)
536
- expect(@store['person0'].related._id).to eq(id1)
537
- expect(@store['person0'].related.related._id).to eq(id2)
538
-
539
- @store.copy(@db_file_new, { :engine => PEROBS::FlatFileDB })
540
- @store.delete_store
541
-
542
- @store = PEROBS::Store.new(@db_file_new, { :engine => PEROBS::FlatFileDB })
543
- expect(@store['person0']._id).to eq(id0)
544
- expect(@store['person0'].related._id).to eq(id1)
545
- expect(@store['person0'].related.related._id).to eq(id2)
546
- expect(@store['persons'][0]).to eq(@store['person0'])
547
- expect(@store['persons'][1]).to eq(@store['person0'].related)
548
- 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
549
696
  end
550
697
 
551
698
  end