perobs 3.0.1 → 4.3.0

Sign up to get free protection for your applications and to get access to all the features.
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