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.
- checksums.yaml +5 -5
- data/README.md +19 -18
- data/lib/perobs.rb +2 -0
- data/lib/perobs/Array.rb +68 -21
- data/lib/perobs/BTree.rb +110 -54
- data/lib/perobs/BTreeBlob.rb +14 -13
- data/lib/perobs/BTreeDB.rb +11 -10
- data/lib/perobs/BTreeNode.rb +551 -197
- data/lib/perobs/BTreeNodeCache.rb +10 -8
- data/lib/perobs/BTreeNodeLink.rb +11 -1
- data/lib/perobs/BigArray.rb +285 -0
- data/lib/perobs/BigArrayNode.rb +1002 -0
- data/lib/perobs/BigHash.rb +246 -0
- data/lib/perobs/BigTree.rb +197 -0
- data/lib/perobs/BigTreeNode.rb +873 -0
- data/lib/perobs/Cache.rb +47 -22
- data/lib/perobs/ClassMap.rb +2 -2
- data/lib/perobs/ConsoleProgressMeter.rb +61 -0
- data/lib/perobs/DataBase.rb +4 -3
- data/lib/perobs/DynamoDB.rb +62 -20
- data/lib/perobs/EquiBlobsFile.rb +174 -59
- data/lib/perobs/FNV_Hash_1a_64.rb +54 -0
- data/lib/perobs/FlatFile.rb +536 -242
- data/lib/perobs/FlatFileBlobHeader.rb +120 -84
- data/lib/perobs/FlatFileDB.rb +58 -27
- data/lib/perobs/FuzzyStringMatcher.rb +175 -0
- data/lib/perobs/Hash.rb +129 -35
- data/lib/perobs/IDList.rb +144 -0
- data/lib/perobs/IDListPage.rb +107 -0
- data/lib/perobs/IDListPageFile.rb +180 -0
- data/lib/perobs/IDListPageRecord.rb +142 -0
- data/lib/perobs/LockFile.rb +3 -0
- data/lib/perobs/Object.rb +28 -20
- data/lib/perobs/ObjectBase.rb +53 -10
- data/lib/perobs/PersistentObjectCache.rb +142 -0
- data/lib/perobs/PersistentObjectCacheLine.rb +99 -0
- data/lib/perobs/ProgressMeter.rb +97 -0
- data/lib/perobs/SpaceManager.rb +273 -0
- data/lib/perobs/SpaceTree.rb +63 -47
- data/lib/perobs/SpaceTreeNode.rb +134 -115
- data/lib/perobs/SpaceTreeNodeLink.rb +1 -1
- data/lib/perobs/StackFile.rb +1 -1
- data/lib/perobs/Store.rb +180 -70
- data/lib/perobs/version.rb +1 -1
- data/perobs.gemspec +4 -4
- data/test/Array_spec.rb +48 -39
- data/test/BTreeDB_spec.rb +2 -2
- data/test/BTree_spec.rb +50 -1
- data/test/BigArray_spec.rb +261 -0
- data/test/BigHash_spec.rb +152 -0
- data/test/BigTreeNode_spec.rb +153 -0
- data/test/BigTree_spec.rb +259 -0
- data/test/EquiBlobsFile_spec.rb +105 -5
- data/test/FNV_Hash_1a_64_spec.rb +59 -0
- data/test/FlatFileDB_spec.rb +199 -15
- data/test/FuzzyStringMatcher_spec.rb +261 -0
- data/test/Hash_spec.rb +27 -16
- data/test/IDList_spec.rb +77 -0
- data/test/LegacyDBs/LegacyDB.rb +155 -0
- data/test/LegacyDBs/version_3/class_map.json +1 -0
- data/test/LegacyDBs/version_3/config.json +1 -0
- data/test/LegacyDBs/version_3/database.blobs +0 -0
- data/test/LegacyDBs/version_3/database_spaces.blobs +0 -0
- data/test/LegacyDBs/version_3/index.blobs +0 -0
- data/test/LegacyDBs/version_3/version +1 -0
- data/test/LockFile_spec.rb +9 -6
- data/test/Object_spec.rb +5 -5
- data/test/SpaceManager_spec.rb +176 -0
- data/test/SpaceTree_spec.rb +27 -9
- data/test/Store_spec.rb +353 -206
- data/test/perobs_spec.rb +7 -3
- data/test/spec_helper.rb +9 -4
- metadata +59 -16
- data/lib/perobs/SpaceTreeNodeCache.rb +0 -76
- 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"}}
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
3
|
data/test/LockFile_spec.rb
CHANGED
@@ -30,7 +30,11 @@ require 'perobs/LockFile'
|
|
30
30
|
describe PEROBS::LockFile do
|
31
31
|
|
32
32
|
before(:each) do
|
33
|
-
|
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
|
-
|
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
|
-
|
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.
|
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.
|
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.
|
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
|
data/test/SpaceTree_spec.rb
CHANGED
@@ -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.
|
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.
|
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
|
-
|
212
|
-
|
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
|
-
|
232
|
-
|
233
|
-
|
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
|
-
|
34
|
+
attr_persist :name, :zip, :bmi, :married, :related, :relatives
|
35
35
|
|
36
|
-
def initialize(
|
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
|
-
|
47
|
+
attr_persist :name, :zip, :bmi, :married, :related, :relatives
|
48
48
|
|
49
|
-
def initialize(
|
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
|
-
|
60
|
+
attr_persist :child
|
61
61
|
|
62
|
-
def initialize(
|
62
|
+
def initialize(p)
|
63
63
|
super
|
64
|
-
self.child =
|
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
|
-
|
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
|
-
|
98
|
-
|
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
|
-
|
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 =
|
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
|
-
|
123
|
-
|
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
|
-
|
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
|
-
|
131
|
+
capture_io { store.exit }
|
134
132
|
|
135
|
-
|
136
|
-
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 =
|
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
|
-
|
152
|
+
store = PEROBS::Store.new(@db_file, :cache_bits => 3)
|
151
153
|
last_obj = nil
|
152
154
|
0.upto(20) do |i|
|
153
|
-
|
154
|
-
expect(
|
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(
|
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
|
-
|
168
|
-
|
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
|
-
|
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
|
-
|
184
|
+
capture_io { store.exit }
|
179
185
|
|
180
|
-
|
181
|
-
|
182
|
-
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 =
|
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
|
-
|
205
|
+
store = PEROBS::Store.new(@db_file, :cache_bits => 3)
|
196
206
|
0.upto(20) do |i|
|
197
|
-
|
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
|
-
|
211
|
+
store["person#{i}"].name = "New Person #{i}"
|
202
212
|
end
|
203
|
-
|
204
|
-
|
213
|
+
capture_io { store.exit }
|
214
|
+
store = PEROBS::Store.new(@db_file)
|
205
215
|
0.upto(20) do |i|
|
206
|
-
expect(
|
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
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
expect(
|
224
|
-
|
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
|
-
|
229
|
-
|
274
|
+
store = PEROBS::Store.new(@db_file)
|
275
|
+
store['person0'] = p0 = store.new(Person)
|
230
276
|
id0 = p0._id
|
231
|
-
p1 =
|
277
|
+
p1 = store.new(Person)
|
232
278
|
id1 = p1._id
|
233
|
-
p2 =
|
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(
|
239
|
-
expect(
|
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
|
-
|
242
|
-
|
243
|
-
expect(
|
244
|
-
expect(
|
245
|
-
expect(
|
246
|
-
|
247
|
-
|
248
|
-
expect(
|
249
|
-
|
250
|
-
expect(
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
expect(
|
255
|
-
expect(
|
256
|
-
expect(
|
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
|
-
|
261
|
-
|
262
|
-
|
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(
|
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
|
-
|
323
|
+
store = PEROBS::Store.new(@db_file)
|
270
324
|
begin
|
271
|
-
|
272
|
-
|
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(
|
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
|
-
|
283
|
-
|
340
|
+
store = PEROBS::Store.new(@db_file)
|
341
|
+
store['person1'] = p1 = store.new(Person)
|
284
342
|
p1.name = 'Joe'
|
285
343
|
begin
|
286
|
-
|
287
|
-
|
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(
|
294
|
-
expect(
|
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
|
-
|
299
|
-
|
300
|
-
|
360
|
+
store = PEROBS::Store.new(@db_file)
|
361
|
+
store.transaction do
|
362
|
+
store['person0'] = p0 = store.new(Person)
|
301
363
|
p0.name = 'Jimmy'
|
302
|
-
|
303
|
-
|
364
|
+
store.transaction do
|
365
|
+
store['person1'] = p1 = store.new(Person)
|
304
366
|
p1.name = 'Joe'
|
305
367
|
end
|
306
368
|
end
|
307
|
-
expect(
|
308
|
-
expect(
|
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
|
-
|
378
|
+
store = PEROBS::Store.new(@db_file)
|
313
379
|
begin
|
314
|
-
|
315
|
-
|
380
|
+
store.transaction do
|
381
|
+
store['person0'] = p0 = store.new(Person)
|
316
382
|
p0.name = 'Jimmy'
|
317
383
|
begin
|
318
|
-
|
319
|
-
|
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(
|
329
|
-
expect(
|
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
|
-
|
403
|
+
store = PEROBS::Store.new(@db_file)
|
334
404
|
begin
|
335
|
-
|
336
|
-
|
405
|
+
store.transaction do
|
406
|
+
store['person0'] = p0 = store.new(Person)
|
337
407
|
p0.name = 'Jimmy'
|
338
|
-
|
339
|
-
|
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(
|
347
|
-
expect(
|
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
|
-
|
352
|
-
|
353
|
-
|
425
|
+
store = PEROBS::Store.new(@db_file)
|
426
|
+
store.transaction do
|
427
|
+
store['person0'] = p0 = store.new(Person)
|
354
428
|
p0.name = 'Jimmy'
|
355
|
-
|
356
|
-
|
429
|
+
store.transaction do
|
430
|
+
store['person1'] = p1 = store.new(Person)
|
357
431
|
p1.name = 'Joe'
|
358
|
-
|
359
|
-
|
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(
|
365
|
-
expect(
|
366
|
-
expect(
|
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
|
-
|
371
|
-
|
372
|
-
|
448
|
+
store = PEROBS::Store.new(@db_file)
|
449
|
+
store.transaction do
|
450
|
+
store['person0'] = p0 = store.new(Person)
|
373
451
|
p0.name = 'Jimmy'
|
374
|
-
|
375
|
-
|
452
|
+
store.transaction do
|
453
|
+
store['person1'] = p1 = store.new(Person)
|
376
454
|
p1.name = 'Joe'
|
377
455
|
begin
|
378
|
-
|
379
|
-
|
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(
|
388
|
-
expect(
|
389
|
-
expect(
|
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
|
-
|
394
|
-
|
395
|
-
|
475
|
+
store = PEROBS::Store.new(@db_file)
|
476
|
+
store.transaction do
|
477
|
+
store['person0'] = p0 = store.new(Person)
|
396
478
|
p0.name = 'Jimmy'
|
397
|
-
|
398
|
-
|
479
|
+
store.transaction do
|
480
|
+
store['person1'] = p1 = store.new(Person)
|
399
481
|
p1.name = 'Joe'
|
400
482
|
begin
|
401
|
-
|
402
|
-
|
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(
|
412
|
-
expect(
|
413
|
-
expect(
|
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
|
-
|
418
|
-
expect(
|
419
|
-
|
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(
|
422
|
-
|
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
|
-
|
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
|
-
|
430
|
-
|
431
|
-
|
432
|
-
expect(
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
expect(
|
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
|
-
|
570
|
+
store = PEROBS::Store.new(@db_file, options)
|
442
571
|
ref = {}
|
443
572
|
|
444
573
|
deletions_since_last_gc = 0
|
445
|
-
0.upto(
|
574
|
+
0.upto(5000) do |i|
|
446
575
|
key = "o#{i}"
|
447
|
-
case rand(
|
576
|
+
case rand(9)
|
448
577
|
when 0
|
449
578
|
# Add 'A' person
|
450
|
-
value = 'A' * rand(512)
|
451
|
-
|
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(
|
457
|
-
|
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
|
464
|
-
expect(
|
465
|
-
|
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
|
-
|
473
|
-
stats =
|
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(
|
616
|
+
capture_io { expect(store.gc).to eq(deletions_since_last_gc) }
|
478
617
|
end
|
479
|
-
when
|
618
|
+
when 5
|
480
619
|
# Sync store and reload
|
481
620
|
if rand(15) == 0
|
482
|
-
|
483
|
-
|
621
|
+
capture_io { store.exit }
|
622
|
+
store = PEROBS::Store.new(@db_file, options)
|
484
623
|
end
|
485
|
-
when
|
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 = '
|
490
|
-
|
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
|
634
|
+
when 7
|
496
635
|
# Sync and check store
|
497
636
|
if rand(50) == 0
|
498
|
-
|
499
|
-
expect(
|
637
|
+
#store.sync
|
638
|
+
capture_io { expect(store.check(false)).to eq(0) }
|
500
639
|
end
|
501
|
-
when
|
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
|
505
|
-
expect(
|
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(
|
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(
|
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
|
-
|
520
|
-
|
662
|
+
store = PEROBS::Store.new(@db_file)
|
663
|
+
store['person0'] = p0 = store.new(Person)
|
521
664
|
id0 = p0._id
|
522
|
-
p1 =
|
665
|
+
p1 = store.new(Person)
|
523
666
|
id1 = p1._id
|
524
|
-
p2 =
|
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 =
|
530
|
-
|
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(
|
536
|
-
expect(
|
537
|
-
expect(
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
expect(
|
544
|
-
expect(
|
545
|
-
expect(
|
546
|
-
expect(
|
547
|
-
expect(
|
548
|
-
expect(
|
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
|