perobs 0.0.1

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.
@@ -0,0 +1,108 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright (c) 2015 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
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
27
+
28
+ require 'fileutils'
29
+ require 'time'
30
+ require 'perobs'
31
+
32
+ class O1 < PEROBS::Object
33
+
34
+ po_attr :a1
35
+
36
+ def initialize(store)
37
+ super
38
+ end
39
+
40
+ end
41
+
42
+ class O2 < PEROBS::Object
43
+
44
+ po_attr :a1, :a2, :a3, :a4
45
+
46
+ def initialize(store)
47
+ super
48
+ init_attr(:a1, 'a1')
49
+ init_attr(:a2, nil)
50
+ init_attr(:a4, 42)
51
+ end
52
+
53
+ end
54
+
55
+ describe PEROBS::Store do
56
+
57
+ before(:each) do
58
+ FileUtils.rm_rf('test_db')
59
+ @store = PEROBS::Store.new('test_db')
60
+ end
61
+
62
+ after(:all) do
63
+ FileUtils.rm_rf('test_db')
64
+ end
65
+
66
+ it 'should initialize attributes with default values' do
67
+ @store['o1'] = o1 = O1.new(@store)
68
+ @store['o2'] = o2 = O2.new(@store)
69
+ o2.a1.should == 'a1'
70
+ o2.a2.should be_nil
71
+ o2.a3.should be_nil
72
+ o2.a4.should == 42
73
+ end
74
+
75
+ it 'should assign values to attributes' do
76
+ @store['o1'] = o1 = O1.new(@store)
77
+ @store['o2'] = o2 = O2.new(@store)
78
+ o1.a1 = 'a1'
79
+ o2.a1 = nil
80
+ o2.a3 = o1
81
+
82
+ o1.a1.should == 'a1'
83
+ o2.a1.should be_nil
84
+ o2.a3.should == o1
85
+ o2.a4.should == 42
86
+ @store.sync
87
+ end
88
+
89
+ it 'should persist assigned values' do
90
+ @store['o1'] = o1 = O1.new(@store)
91
+ @store['o2'] = o2 = O2.new(@store)
92
+ o1.a1 = 'a1'
93
+ o2.a1 = nil
94
+ o2.a3 = o1
95
+ o2.a4 = PEROBS::Array.new(@store)
96
+ o2.a4 += [ 0, 1, 2 ]
97
+ @store.sync
98
+
99
+ @store = PEROBS::Store.new('test_db')
100
+ o1 = @store['o1']
101
+ o2 = @store['o2']
102
+ o1.a1.should == 'a1'
103
+ o2.a1.should be_nil
104
+ o2.a3.should == o1
105
+ o2.a4.should == [ 0, 1, 2 ]
106
+ end
107
+
108
+ end
@@ -0,0 +1,412 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright (c) 2015 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
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
27
+
28
+ require 'fileutils'
29
+ require 'time'
30
+ require 'perobs'
31
+
32
+ class POSError < RuntimeError
33
+ end
34
+
35
+ class Person < PEROBS::Object
36
+
37
+ po_attr :name, :zip, :bmi, :married, :related, :relatives
38
+
39
+ def initialize(store)
40
+ super
41
+ init_attr(:name, '')
42
+ init_attr(:bmi, 22.2)
43
+ init_attr(:married, false)
44
+ end
45
+
46
+ end
47
+
48
+ describe PEROBS::Store do
49
+
50
+ before(:all) do
51
+ FileUtils.rm_rf('test_db')
52
+ end
53
+
54
+ after(:each) do
55
+ @store.gc
56
+ lambda { @store.check }.should_not raise_error
57
+ FileUtils.rm_rf('test_db')
58
+ end
59
+
60
+ it 'should @store simple objects' do
61
+ @store = PEROBS::Store.new('test_db', { :serializer => :yaml })
62
+ @store['john'] = john = Person.new(@store)
63
+ john.name = 'John'
64
+ john.zip = 4060
65
+ john.bmi = 25.5
66
+ @store['jane'] = jane = Person.new(@store)
67
+ jane.name = 'Jane'
68
+ jane.related = john
69
+ jane.married = true
70
+ jane.relatives = 'test'
71
+
72
+ john.name.should == 'John'
73
+ john.zip.should == 4060
74
+ john.bmi.should == 25.5
75
+ john.married.should be_false
76
+ john.related.should be_nil
77
+ jane = @store['jane']
78
+ jane.name.should == 'Jane'
79
+ jane.related.should == john
80
+ jane.married.should be_true
81
+ end
82
+
83
+ it 'should @store and retrieve simple objects' do
84
+ [ :marshal, :json, :yaml ].each do |serializer|
85
+ FileUtils.rm_rf('test_db')
86
+ @store = PEROBS::Store.new('test_db', { :serializer => serializer })
87
+ @store['john'] = john = Person.new(@store)
88
+ john.name = 'John'
89
+ john.zip = 4060
90
+ john.bmi = 25.5
91
+ @store['jane'] = jane = Person.new(@store)
92
+ jane.name = 'Jane'
93
+ jane.related = john
94
+ jane.married = true
95
+ jane.relatives = 'test'
96
+
97
+ @store.sync
98
+
99
+ @store = PEROBS::Store.new('test_db', { :serializer => serializer })
100
+ john = @store['john']
101
+ john.name.should == 'John'
102
+ john.zip.should == 4060
103
+ john.bmi.should == 25.5
104
+ john.married.should be_false
105
+ john.related.should be_nil
106
+ jane = @store['jane']
107
+ jane.name.should == 'Jane'
108
+ jane.related.should == john
109
+ jane.married.should be_true
110
+ end
111
+ end
112
+
113
+ it 'should flush cached objects when necessary' do
114
+ @store = PEROBS::Store.new('test_db', :cache_bits => 3)
115
+ last_obj = nil
116
+ 0.upto(20) do |i|
117
+ @store["person#{i}"] = obj = Person.new(@store)
118
+ @store["person#{i}"].should == obj
119
+ obj.name = "Person #{i}"
120
+ obj.name.should == "Person #{i}"
121
+ obj.related = last_obj
122
+ obj.related.should == last_obj
123
+ last_obj = obj
124
+ end
125
+ 0.upto(20) do |i|
126
+ @store["person#{i}"].name.should == "Person #{i}"
127
+ end
128
+ end
129
+
130
+ it 'should detect modification to non-working objects' do
131
+ @store = PEROBS::Store.new('test_db', :cache_bits => 3)
132
+ 0.upto(20) do |i|
133
+ @store["person#{i}"] = obj = Person.new(@store)
134
+ obj.name = "Person #{i}"
135
+ end
136
+ 0.upto(20) do |i|
137
+ @store["person#{i}"].name = "New Person #{i}"
138
+ end
139
+ @store.sync
140
+ @store = PEROBS::Store.new('test_db')
141
+ 0.upto(20) do |i|
142
+ @store["person#{i}"].name.should == "New Person #{i}"
143
+ end
144
+ end
145
+
146
+ it 'should garbage collect unlinked objects' do
147
+ @store = PEROBS::Store.new('test_db')
148
+ @store['person1'] = obj = Person.new(@store)
149
+ id1 = obj._id
150
+ @store['person2'] = obj = Person.new(@store)
151
+ id2 = obj._id
152
+ obj.related = obj = Person.new(@store)
153
+ id3 = obj._id
154
+ @store.sync
155
+ @store['person1'] = nil
156
+ @store.gc
157
+ @store = PEROBS::Store.new('test_db')
158
+ @store.object_by_id(id1).should be_nil
159
+ @store['person2']._id.should == id2
160
+ @store['person2'].related._id.should == id3
161
+ end
162
+
163
+ it 'should handle cyclicly linked objects' do
164
+ @store = PEROBS::Store.new('test_db')
165
+ @store['person0'] = p0 = Person.new(@store)
166
+ id0 = p0._id
167
+ p1 = Person.new(@store)
168
+ id1 = p1._id
169
+ p2 = Person.new(@store)
170
+ id2 = p2._id
171
+ p1.related = p2
172
+ p2.related = p1
173
+ p0.related = p1
174
+ @store.sync
175
+ @store.gc
176
+ @store = PEROBS::Store.new('test_db')
177
+ @store['person0']._id.should == id0
178
+ @store['person0'].related._id.should == id1
179
+ @store['person0'].related.related._id.should == id2
180
+
181
+ @store['person0'].related = nil
182
+ @store.gc
183
+ @store.object_by_id(id1).should be_nil
184
+ @store.object_by_id(id2).should be_nil
185
+
186
+ @store = PEROBS::Store.new('test_db')
187
+ @store.object_by_id(id1).should be_nil
188
+ @store.object_by_id(id2).should be_nil
189
+ end
190
+
191
+ it 'should support a successful transaction' do
192
+ @store = PEROBS::Store.new('test_db')
193
+ @store.transaction do
194
+ @store['person0'] = p0 = Person.new(@store)
195
+ p0.name = 'Jimmy'
196
+ end
197
+ @store['person0'].name.should == 'Jimmy'
198
+ end
199
+
200
+ it 'should handle a failed transaction 1' do
201
+ @store = PEROBS::Store.new('test_db')
202
+ begin
203
+ @store.transaction do
204
+ @store['person0'] = p0 = Person.new(@store)
205
+ p0.name = 'Jimmy'
206
+ raise POSError
207
+ end
208
+ rescue POSError
209
+ end
210
+ @store['person0'].should be_nil
211
+ end
212
+
213
+ it 'should handle a failed transaction 2' do
214
+ @store = PEROBS::Store.new('test_db')
215
+ @store['person1'] = p1 = Person.new(@store)
216
+ p1.name = 'Joe'
217
+ begin
218
+ @store.transaction do
219
+ @store['person0'] = p0 = Person.new(@store)
220
+ p0.name = 'Jimmy'
221
+ raise POSError
222
+ end
223
+ rescue POSError
224
+ end
225
+ @store['person1'].name.should == 'Joe'
226
+ @store['person0'].should be_nil
227
+ end
228
+
229
+ it 'should support a successful nested transaction' do
230
+ @store = PEROBS::Store.new('test_db')
231
+ @store.transaction do
232
+ @store['person0'] = p0 = Person.new(@store)
233
+ p0.name = 'Jimmy'
234
+ @store.transaction do
235
+ @store['person1'] = p1 = Person.new(@store)
236
+ p1.name = 'Joe'
237
+ end
238
+ end
239
+ @store['person0'].name.should == 'Jimmy'
240
+ @store['person1'].name.should == 'Joe'
241
+ end
242
+
243
+ it 'should handle a failed nested transaction 1' do
244
+ @store = PEROBS::Store.new('test_db')
245
+ begin
246
+ @store.transaction do
247
+ @store['person0'] = p0 = Person.new(@store)
248
+ p0.name = 'Jimmy'
249
+ begin
250
+ @store.transaction do
251
+ @store['person1'] = p1 = Person.new(@store)
252
+ p1.name = 'Joe'
253
+ raise POSError
254
+ end
255
+ rescue POSError
256
+ end
257
+ end
258
+ rescue POSError
259
+ end
260
+ @store['person0'].name.should == 'Jimmy'
261
+ @store['person1'].should be_nil
262
+ end
263
+
264
+ it 'should handle a failed nested transaction 2' do
265
+ @store = PEROBS::Store.new('test_db')
266
+ begin
267
+ @store.transaction do
268
+ @store['person0'] = p0 = Person.new(@store)
269
+ p0.name = 'Jimmy'
270
+ @store.transaction do
271
+ @store['person1'] = p1 = Person.new(@store)
272
+ p1.name = 'Joe'
273
+ end
274
+ raise POSError
275
+ end
276
+ rescue POSError
277
+ end
278
+ @store['person0'].should be_nil
279
+ @store['person1'].should be_nil
280
+ end
281
+
282
+ it 'should support a successful 2-level nested transaction' do
283
+ @store = PEROBS::Store.new('test_db')
284
+ @store.transaction do
285
+ @store['person0'] = p0 = Person.new(@store)
286
+ p0.name = 'Jimmy'
287
+ @store.transaction do
288
+ @store['person1'] = p1 = Person.new(@store)
289
+ p1.name = 'Joe'
290
+ @store.transaction do
291
+ @store['person2'] = p2 = Person.new(@store)
292
+ p2.name = 'Jane'
293
+ end
294
+ end
295
+ end
296
+ @store['person0'].name.should == 'Jimmy'
297
+ @store['person1'].name.should == 'Joe'
298
+ @store['person2'].name.should == 'Jane'
299
+ end
300
+
301
+ it 'should handle a failed 2-level nested transaction 1' do
302
+ @store = PEROBS::Store.new('test_db')
303
+ @store.transaction do
304
+ @store['person0'] = p0 = Person.new(@store)
305
+ p0.name = 'Jimmy'
306
+ @store.transaction do
307
+ @store['person1'] = p1 = Person.new(@store)
308
+ p1.name = 'Joe'
309
+ begin
310
+ @store.transaction do
311
+ @store['person2'] = p2 = Person.new(@store)
312
+ p2.name = 'Jane'
313
+ raise POSError
314
+ end
315
+ rescue POSError
316
+ end
317
+ end
318
+ end
319
+ @store['person0'].name.should == 'Jimmy'
320
+ @store['person1'].name.should == 'Joe'
321
+ @store['person2'].should be_nil
322
+ end
323
+
324
+ it 'should handle a failed 2-level nested transaction 2' do
325
+ @store = PEROBS::Store.new('test_db')
326
+ @store.transaction do
327
+ @store['person0'] = p0 = Person.new(@store)
328
+ p0.name = 'Jimmy'
329
+ @store.transaction do
330
+ @store['person1'] = p1 = Person.new(@store)
331
+ p1.name = 'Joe'
332
+ begin
333
+ @store.transaction do
334
+ @store['person2'] = p2 = Person.new(@store)
335
+ p2.name = 'Jane'
336
+ raise POSError
337
+ end
338
+ rescue POSError
339
+ end
340
+ p1.name = 'Jane'
341
+ end
342
+ end
343
+ @store['person0'].name.should == 'Jimmy'
344
+ @store['person1'].name.should == 'Jane'
345
+ @store['person2'].should be_nil
346
+ end
347
+
348
+ it 'should survive a real world usage test' do
349
+ options = { :engine => PEROBS::HashedBlocksDB, :dir_nibbles => 1 }
350
+ @store = PEROBS::Store.new('test_db', options)
351
+ ref = {}
352
+
353
+ 0.upto(2000) do |i|
354
+ key = "o#{i}"
355
+ case i % 8
356
+ when 0
357
+ value = 'A' * rand(512)
358
+ @store[key] = p = Person.new(@store)
359
+ p.name = value
360
+ ref[key] = value
361
+ @store.sync
362
+ when 1
363
+ value = 'B' * rand(128)
364
+ @store[key] = p = Person.new(@store)
365
+ p.name = value
366
+ ref[key] = value
367
+ when 2
368
+ index = i - rand(20)
369
+ if index >= 0
370
+ key = "o#{i - rand(20)}"
371
+ @store[key] = nil
372
+ ref.delete(key)
373
+ end
374
+ when 3
375
+ @store.gc if rand(30) == 0
376
+ when 4
377
+ if rand(15) == 0
378
+ @store.sync
379
+ @store = PEROBS::Store.new('test_db', options)
380
+ end
381
+ when 5
382
+ index = i - rand(10)
383
+ if rand(3) == 0 && index >= 0
384
+ key = "o#{i - rand(10)}"
385
+ value = 'C' * rand(1024)
386
+ @store[key] = p = Person.new(@store)
387
+ p.name = value
388
+ ref[key] = value
389
+ end
390
+ when 6
391
+ if rand(50) == 0
392
+ @store.sync
393
+ @store.check(false)
394
+ end
395
+ when 7
396
+ index = rand(i)
397
+ if ref[key]
398
+ @store[key].name.should == ref[key]
399
+ end
400
+ end
401
+
402
+ if ref[key]
403
+ @store[key].name.should == ref[key]
404
+ end
405
+ end
406
+
407
+ ref.each do |k, v|
408
+ @store[k].name.should == v
409
+ end
410
+ end
411
+
412
+ end
@@ -0,0 +1,155 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright (c) 2015 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
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
27
+
28
+ require 'fileutils'
29
+ require 'time'
30
+ require 'perobs'
31
+
32
+ class Person < PEROBS::Object
33
+
34
+ po_attr :name, :zip, :bmi, :married, :related, :relatives
35
+
36
+ def initialize(store)
37
+ super
38
+ init_attr(:name, '')
39
+ init_attr(:bmi, 22.2)
40
+ init_attr(:married, false)
41
+ end
42
+
43
+ end
44
+
45
+ describe PEROBS::Store do
46
+
47
+ before(:all) do
48
+ FileUtils.rm_rf('test_db')
49
+ end
50
+
51
+ after(:each) do
52
+ FileUtils.rm_rf('test_db')
53
+ end
54
+
55
+ it 'should store simple objects' do
56
+ store = PEROBS::Store.new('test_db')
57
+ store['john'] = john = Person.new(store)
58
+ john.name = 'John'
59
+ john.zip = 4060
60
+ john.bmi = 25.5
61
+ store['jane'] = jane = Person.new(store)
62
+ jane.name = 'Jane'
63
+ jane.related = john
64
+ jane.married = true
65
+ jane.relatives = 'test'
66
+
67
+ john.name.should == 'John'
68
+ john.zip.should == 4060
69
+ john.bmi.should == 25.5
70
+ john.married.should be_false
71
+ john.related.should be_nil
72
+ jane = store['jane']
73
+ jane.name.should == 'Jane'
74
+ jane.related.should == john
75
+ jane.married.should be_true
76
+ end
77
+
78
+ it 'should store and retrieve simple objects' do
79
+ store = PEROBS::Store.new('test_db')
80
+ store['john'] = john = Person.new(store)
81
+ john.name = 'John'
82
+ john.zip = 4060
83
+ john.bmi = 25.5
84
+ store['jane'] = jane = Person.new(store)
85
+ jane.name = 'Jane'
86
+ jane.related = john
87
+ jane.married = true
88
+ jane.relatives = 'test'
89
+
90
+ store.sync
91
+
92
+ store = PEROBS::Store.new('test_db')
93
+ john = store['john']
94
+ john.name.should == 'John'
95
+ john.zip.should == 4060
96
+ john.bmi.should == 25.5
97
+ john.married.should be_false
98
+ john.related.should be_nil
99
+ jane = store['jane']
100
+ jane.name.should == 'Jane'
101
+ jane.related.should == john
102
+ jane.married.should be_true
103
+ end
104
+
105
+ it 'should flush cached objects when necessary' do
106
+ store = PEROBS::Store.new('test_db', :cache_bits => 3)
107
+ last_obj = nil
108
+ 0.upto(20) do |i|
109
+ store["person#{i}"] = obj = Person.new(store)
110
+ store["person#{i}"].should == obj
111
+ obj.name = "Person #{i}"
112
+ obj.name.should == "Person #{i}"
113
+ obj.related = last_obj
114
+ obj.related.should == last_obj
115
+ last_obj = obj
116
+ end
117
+ 0.upto(20) do |i|
118
+ store["person#{i}"].name.should == "Person #{i}"
119
+ end
120
+ end
121
+
122
+ it 'should detect modification to non-working objects' do
123
+ store = PEROBS::Store.new('test_db', :cache_bits => 3)
124
+ 0.upto(20) do |i|
125
+ store["person#{i}"] = obj = Person.new(store)
126
+ obj.name = "Person #{i}"
127
+ end
128
+ 0.upto(20) do |i|
129
+ store["person#{i}"].name = "New Person #{i}"
130
+ end
131
+ store.sync
132
+ store = PEROBS::Store.new('test_db')
133
+ 0.upto(20) do |i|
134
+ store["person#{i}"].name.should == "New Person #{i}"
135
+ end
136
+ end
137
+
138
+ it 'should garbage collect unlinked objects' do
139
+ store = PEROBS::Store.new('test_db')
140
+ store['person1'] = obj = Person.new(store)
141
+ id1 = obj._id
142
+ store['person2'] = obj = Person.new(store)
143
+ id2 = obj._id
144
+ obj.related = obj = Person.new(store)
145
+ id3 = obj._id
146
+ store.sync
147
+ store['person1'] = nil
148
+ store.gc
149
+ store = PEROBS::Store.new('test_db')
150
+ store.object_by_id(id1).should be_nil
151
+ store['person2']._id.should == id2
152
+ store['person2'].related._id.should == id3
153
+ end
154
+
155
+ end