perobs 2.5.0 → 3.0.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.
@@ -0,0 +1,76 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # = SpaceTree.rb -- Persistent Ruby Object Store
4
+ #
5
+ # Copyright (c) 2016, 2017 by Chris Schlaeger <chris@taskjuggler.org>
6
+ #
7
+ # MIT License
8
+ #
9
+ # Permission is hereby granted, free of charge, to any person obtaining
10
+ # a copy of this software and associated documentation files (the
11
+ # "Software"), to deal in the Software without restriction, including
12
+ # without limitation the rights to use, copy, modify, merge, publish,
13
+ # distribute, sublicense, and/or sell copies of the Software, and to
14
+ # permit persons to whom the Software is furnished to do so, subject to
15
+ # the following conditions:
16
+ #
17
+ # The above copyright notice and this permission notice shall be
18
+ # included in all copies or substantial portions of the Software.
19
+ #
20
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
+
28
+ require 'perobs/SpaceTreeNode'
29
+
30
+ module PEROBS
31
+
32
+ class SpaceTreeNodeCache
33
+
34
+ # Simple cache that can hold up to size SpaceTreeNode entries. Entries are
35
+ # hashed with a simple node_address % size function. This keeps the
36
+ # overhead for managing the cache extremely low yet giving an OK
37
+ # probability to have cache hits.
38
+ # @param size [Integer] maximum number of cache entries
39
+ def initialize(size)
40
+ @size = size
41
+ clear
42
+ end
43
+
44
+ # Insert a node into the cache.
45
+ # @param node [SpaceTreeNode]
46
+ def insert(node)
47
+ @entries[node.node_address % @size] = node
48
+ end
49
+
50
+ # Retrieve a node reference from the cache.
51
+ # @param address [Integer] address of the node to retrieve.
52
+ def get(address)
53
+ node = @entries[address % @size]
54
+ # We can have collisions. Check if the cached node really matches the
55
+ # requested address.
56
+ (node && node.node_address == address) ? node : nil
57
+ end
58
+
59
+ # Remove a node from the cache.
60
+ # @param address [Integer] address of node to remove.
61
+ def delete(address)
62
+ index = address % @size
63
+ if (node = @entries[index]) && node.node_address == address
64
+ @entries[index] = nil
65
+ end
66
+ end
67
+
68
+ # Remove all entries from the cache.
69
+ def clear
70
+ @entries = ::Array.new(@size)
71
+ end
72
+
73
+ end
74
+
75
+ end
76
+
@@ -0,0 +1,103 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # = SpaceTreeNodeLink.rb -- Persistent Ruby Object Store
4
+ #
5
+ # Copyright (c) 2016, 2017 by Chris Schlaeger <chris@taskjuggler.org>
6
+ #
7
+ # MIT License
8
+ #
9
+ # Permission is hereby granted, free of charge, to any person obtaining
10
+ # a copy of this software and associated documentation files (the
11
+ # "Software"), to deal in the Software without restriction, including
12
+ # without limitation the rights to use, copy, modify, merge, publish,
13
+ # distribute, sublicense, and/or sell copies of the Software, and to
14
+ # permit persons to whom the Software is furnished to do so, subject to
15
+ # the following conditions:
16
+ #
17
+ # The above copyright notice and this permission notice shall be
18
+ # included in all copies or substantial portions of the Software.
19
+ #
20
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
+
28
+ module PEROBS
29
+
30
+ # This class is used to form the links between the in-memory SpaceTreeNode
31
+ # objects. The link is based on the address of the node in the file. The
32
+ # class objects transparently convert the address into a corresponding
33
+ # SpaceTreeNode object and pass on all method calls.
34
+ class SpaceTreeNodeLink
35
+
36
+ attr_reader :node_address
37
+
38
+ # Create a new SpaceTreeNodeLink object.
39
+ # @param tree [SpaceTree] The SpaceTree that holds the nodes.
40
+ # @param node_or_address [SpaceTreeNode or SpaceTreeNodeLink or Integer] a
41
+ # SpaceTreeNode, SpaceTreeNodeLink reference or the node
42
+ # address in the file.
43
+ def initialize(tree, node_or_address)
44
+ @tree = tree
45
+ if node_or_address.is_a?(SpaceTreeNode) ||
46
+ node_or_address.is_a?(SpaceTreeNodeLink)
47
+ @node_address = node_or_address.node_address
48
+ elsif node_or_address.is_a?(Integer)
49
+ @node_address = node_or_address
50
+ else
51
+ PEROBS.log.fatal "Unsupported argument type #{node_or_address.class}"
52
+ end
53
+ end
54
+
55
+ # All calls to a SpaceTreeNodeLink object will be forwarded to the
56
+ # corresponding SpaceTreeNode object. If that
57
+ def method_missing(method, *args, &block)
58
+ get_node.send(method, *args, &block)
59
+ end
60
+
61
+ # Make it properly introspectable.
62
+ def respond_to?(method, include_private = false)
63
+ get_node.respond_to?(method)
64
+ end
65
+
66
+ # Compare this node to another node.
67
+ # @return [Boolean] true if node address is identical, false otherwise
68
+ def ==(node)
69
+ @node_address == node.node_address
70
+ end
71
+
72
+ # Compare this node to another node.
73
+ # @return [Boolean] true if node address is not identical, false otherwise
74
+ def !=(node)
75
+ @node_address != node.node_address
76
+ end
77
+
78
+ # Check the link to a sub-node. This method silently ignores all errors if
79
+ # the sub-node does not exist.
80
+ # @return [Boolean] True if link is OK, false otherweise
81
+ def check_node_link(branch, stack)
82
+ begin
83
+ return get_node.check_node_link(branch, stack)
84
+ rescue
85
+ return false
86
+ end
87
+ end
88
+
89
+ # @return Textual version of the SpaceTreeNode
90
+ def to_s
91
+ get_node.to_s
92
+ end
93
+
94
+ private
95
+
96
+ def get_node
97
+ @tree.get_node(@node_address)
98
+ end
99
+
100
+ end
101
+
102
+ end
103
+
data/lib/perobs/Store.rb CHANGED
@@ -347,28 +347,42 @@ module PEROBS
347
347
  # All objects must have in-db version.
348
348
  sync
349
349
  # Run basic consistency checks first.
350
- @db.check_db(repair)
350
+ errors = @db.check_db(repair)
351
351
 
352
352
  # We will use the mark to mark all objects that we have checked already.
353
353
  # Before we start, we need to clear all marks.
354
354
  @db.clear_marks
355
355
 
356
- errors = 0
357
356
  objects = 0
358
357
  @root_objects.each do |name, id|
359
358
  objects += 1
360
359
  errors += check_object(id, repair)
361
360
  end
361
+
362
+ # Delete all broken root objects.
363
+ if repair
364
+ @root_objects.delete_if do |name, id|
365
+ unless (res = @db.check(id, repair))
366
+ PEROBS.log.error "Discarding broken root object '#{name}' " +
367
+ "with ID #{id}"
368
+ errors += 1
369
+ end
370
+ !res
371
+ end
372
+ end
373
+
362
374
  if errors > 0
363
- PEROBS.log.warn "#{errors} errors found in #{objects} objects"
375
+ if repair
376
+ PEROBS.log.error "#{errors} errors found in #{objects} objects"
377
+ else
378
+ PEROBS.log.fatal "#{errors} errors found in #{objects} objects"
379
+ end
364
380
  else
365
381
  PEROBS.log.debug "No errors found"
366
382
  end
367
- # Delete all broken root objects.
368
- @root_objects.delete_if { |name, id| !@db.check(id, repair) }
369
383
 
370
384
  # Ensure that any fixes are written into the DB.
371
- sync
385
+ sync if repair
372
386
 
373
387
  errors
374
388
  end
@@ -504,7 +518,7 @@ module PEROBS
504
518
  # Get the next PEROBS object to check
505
519
  ref_obj, id = todo_list.pop
506
520
 
507
- if (obj = object_by_id(id)) && @db.check(id, repair)
521
+ if (obj = object_by_id(id)) && (obj_ok = @db.check(id, repair))
508
522
  # The object exists and is OK. Mark is as checked.
509
523
  @db.mark(id)
510
524
  # Now look at all other objects referenced by this object.
@@ -517,15 +531,15 @@ module PEROBS
517
531
  # Remove references to bad objects.
518
532
  if ref_obj
519
533
  if repair
520
- PEROBS.log.warn "Eliminating broken reference to object #{id} " +
521
- "in object #{ref_obj._id}:\n" + ref_obj.inspect
534
+ PEROBS.log.error "Removing reference to " +
535
+ "#{obj ? 'broken' : 'non-existing'} object #{id} from:\n" +
536
+ ref_obj.inspect
522
537
  ref_obj._delete_reference_to_id(id)
523
538
  else
524
- PEROBS.log.fatal "The following object references a " +
525
- "non-existing object #{id}:\n" + ref_obj.inspect
539
+ PEROBS.log.error "The following object references a " +
540
+ "#{obj ? 'broken' : 'non-existing'} object #{id}:\n" +
541
+ ref_obj.inspect
526
542
  end
527
- else
528
- PEROBS.log.warn "Eliminating root object #{id}"
529
543
  end
530
544
  errors += 1
531
545
  end
@@ -1,4 +1,4 @@
1
1
  module PEROBS
2
2
  # The version number
3
- VERSION = "2.5.0"
3
+ VERSION = "3.0.0"
4
4
  end
@@ -0,0 +1,128 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright (c) 2016, 2017 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/BTree'
30
+
31
+ describe PEROBS::BTree do
32
+
33
+ before(:all) do
34
+ @db_dir = generate_db_name('BTree')
35
+ FileUtils.mkdir_p(@db_dir)
36
+ @m = PEROBS::BTree.new(@db_dir, 'btree', 11)
37
+ end
38
+
39
+ after(:all) do
40
+ FileUtils.rm_rf(@db_dir)
41
+ end
42
+
43
+ it 'should gracefully handle calling close when not open' do
44
+ @m.close
45
+ end
46
+
47
+ it 'should open the BTree' do
48
+ @m.open
49
+ expect(@m.to_s).to eql("o--- @1\n")
50
+ #expect(@m.to_a).to eql([])
51
+ end
52
+
53
+ it 'should support adding sequential key/value pairs' do
54
+ 0.upto(100) do |i|
55
+ @m.insert(i, 2 * i)
56
+ expect(@m.check).to be true
57
+ expect(@m.get(i)).to eql(2 * i)
58
+ end
59
+ end
60
+
61
+ it 'should persist the data' do
62
+ @m.close
63
+ @m.open
64
+ expect(@m.check).to be true
65
+ 0.upto(100) do |i|
66
+ expect(@m.get(i)).to eql(2 * i)
67
+ end
68
+ end
69
+
70
+ it 'should iterate over the stored key and value pairs' do
71
+ i = 0
72
+ @m.each do |k, v|
73
+ expect(k).to eql(i)
74
+ expect(v).to eql(2 * i)
75
+ i += 1
76
+ end
77
+ expect(i).to eql(101)
78
+ end
79
+
80
+ it 'should yield the key/value pairs on check' do
81
+ i = 0
82
+ @m.check do |k, v|
83
+ expect(k).to eql(k)
84
+ expect(v).to eql(2 * k)
85
+ i += 1
86
+ end
87
+ expect(i).to eql(101)
88
+ end
89
+
90
+ it 'should support clearing the tree' do
91
+ @m.clear
92
+ expect(@m.check).to be true
93
+ end
94
+
95
+ it 'should support erasing the backing store' do
96
+ @m.close
97
+ @m.erase
98
+ @m.open
99
+ expect(@m.check).to be true
100
+ end
101
+
102
+ it 'should support adding random key/value pairs' do
103
+ (1..1000).to_a.shuffle.each do |i|
104
+ @m.insert(i, i * 100)
105
+ expect(@m.check).to be true
106
+ end
107
+ (1..1000).to_a.shuffle.each do |i|
108
+ expect(@m.get(i)).to eql(i * 100)
109
+ end
110
+ end
111
+
112
+ it 'should support removing keys' do
113
+ @m.clear
114
+ @m.insert(1, 1)
115
+ expect(@m.remove(1)).to eql(1)
116
+ expect(@m.check).to be true
117
+ expect(@m.to_s).to eql("o--- @1\n")
118
+
119
+ (1..100).to_a.shuffle.each do |i|
120
+ @m.insert(i, i * 100)
121
+ end
122
+ (1..100).to_a.shuffle.each do |i|
123
+ expect(@m.remove(i)).to eql(i * 100)
124
+ expect(@m.check).to be true
125
+ end
126
+ end
127
+
128
+ end
@@ -0,0 +1,199 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright (c) 2016, 2017 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/EquiBlobsFile'
30
+
31
+ describe PEROBS::EquiBlobsFile do
32
+
33
+ before(:all) do
34
+ @db_dir = generate_db_name('EquiBlobsFile')
35
+ FileUtils.mkdir_p(@db_dir)
36
+ @bf = PEROBS::EquiBlobsFile.new(@db_dir, 'EquiBlobsFile', 8)
37
+ end
38
+
39
+ after(:all) do
40
+ FileUtils.rm_rf(@db_dir)
41
+ end
42
+
43
+ it 'should create the file' do
44
+ @bf.open
45
+ expect(@bf.total_entries).to eql(0)
46
+ expect(@bf.total_spaces).to eql(0)
47
+ expect(@bf.check).to be true
48
+ end
49
+
50
+ it 'should return free addresses' do
51
+ expect(@bf.free_address).to eql(1)
52
+ end
53
+
54
+ it 'should store and retrieve a blob' do
55
+ @bf.store_blob(1,'00000000')
56
+ expect(@bf.total_entries).to eql(1)
57
+ expect(@bf.check).to be true
58
+ expect(@bf.retrieve_blob(1)).to eql('00000000')
59
+ expect(@bf.check).to be true
60
+ end
61
+
62
+ it 'should store and retrieve multiple blobs' do
63
+ @bf.store_blob(1,'XXXXXXXX')
64
+ expect(@bf.total_entries).to eql(1)
65
+ expect(@bf.check).to be true
66
+ @bf.store_blob(2,'11111111')
67
+ expect(@bf.check).to be true
68
+ @bf.store_blob(3,'22222222')
69
+ expect(@bf.check).to be true
70
+ @bf.store_blob(4,'33333333')
71
+ expect(@bf.total_entries).to eql(4)
72
+ expect(@bf.check).to be true
73
+ expect(@bf.retrieve_blob(1)).to eql('XXXXXXXX')
74
+ expect(@bf.retrieve_blob(2)).to eql('11111111')
75
+ expect(@bf.retrieve_blob(3)).to eql('22222222')
76
+ expect(@bf.retrieve_blob(4)).to eql('33333333')
77
+ end
78
+
79
+ it 'should raise error for a too large address' do
80
+ PEROBS.log.open(StringIO.new)
81
+ expect { @bf.retrieve_blob(5) }.to raise_error(PEROBS::FatalError)
82
+ PEROBS.log.open($stderr)
83
+ end
84
+
85
+ it 'should delete entries' do
86
+ @bf.delete_blob(3)
87
+ expect(@bf.total_entries).to eql(3)
88
+ expect(@bf.total_spaces).to eql(1)
89
+ expect(@bf.check).to be true
90
+ @bf.delete_blob(2)
91
+ expect(@bf.total_entries).to eql(2)
92
+ expect(@bf.total_spaces).to eql(2)
93
+ expect(@bf.check).to be true
94
+ @bf.delete_blob(1)
95
+ expect(@bf.total_entries).to eql(1)
96
+ expect(@bf.total_spaces).to eql(3)
97
+ expect(@bf.check).to be true
98
+ end
99
+
100
+ it 'should raise error when inserting into a non-reserved cell' do
101
+ PEROBS.log.open(StringIO.new)
102
+ expect { @bf.store_blob(1,'XXXXXXXX') }.to raise_error(PEROBS::FatalError)
103
+ PEROBS.log.open($stderr)
104
+ expect(@bf.total_entries).to eql(1)
105
+ expect(@bf.check).to be true
106
+ end
107
+
108
+ it 'shoud support inserting into deleted cells' do
109
+ expect(@bf.free_address).to eql(1)
110
+ @bf.store_blob(1, '44444444')
111
+ expect(@bf.check).to be true
112
+ end
113
+
114
+ it 'should persist all values over and close/open' do
115
+ @bf.close
116
+ @bf.open
117
+ expect(@bf.total_entries).to eql(2)
118
+ expect(@bf.total_spaces).to eql(2)
119
+ expect(@bf.check).to be true
120
+
121
+ expect(@bf.retrieve_blob(1)).to eql('44444444')
122
+ expect(@bf.retrieve_blob(4)).to eql('33333333')
123
+ end
124
+
125
+ it 'should support inserting into deleted cells (2)' do
126
+ expect(@bf.free_address).to eql(2)
127
+ @bf.store_blob(2,'55555555')
128
+ expect(@bf.check).to be true
129
+
130
+ expect(@bf.free_address).to eql(3)
131
+ @bf.store_blob(3,'66666666')
132
+ expect(@bf.total_entries).to eql(4)
133
+
134
+ expect(@bf.check).to be true
135
+ end
136
+
137
+ it 'should support clearing the file' do
138
+ @bf.clear
139
+ expect(@bf.total_entries).to eql(0)
140
+ expect(@bf.total_spaces).to eql(0)
141
+ expect(@bf.check).to be true
142
+ expect(@bf.free_address).to eql(1)
143
+ @bf.store_blob(1,'00000000')
144
+ expect(@bf.total_entries).to eql(1)
145
+ expect(@bf.check).to be true
146
+ expect(@bf.retrieve_blob(1)).to eql('00000000')
147
+ expect(@bf.check).to be true
148
+ end
149
+
150
+ it 'should support trimming the file' do
151
+ @bf.clear
152
+ 1.upto(8) do |i|
153
+ adr = @bf.free_address
154
+ @bf.store_blob(adr, (0.ord + i).chr * 8)
155
+ end
156
+ expect(@bf.total_entries).to eql(8)
157
+ @bf.delete_blob(1)
158
+ @bf.delete_blob(2)
159
+ @bf.delete_blob(4)
160
+ @bf.delete_blob(5)
161
+ @bf.delete_blob(7)
162
+ expect(@bf.total_entries).to eql(3)
163
+ expect(@bf.total_spaces).to eql(5)
164
+ expect(@bf.check).to be true
165
+
166
+ @bf.delete_blob(8)
167
+ expect(@bf.total_entries).to eql(2)
168
+ expect(@bf.total_spaces).to eql(4)
169
+ expect(@bf.check).to be true
170
+
171
+ @bf.delete_blob(6)
172
+ expect(@bf.total_entries).to eql(1)
173
+ expect(@bf.total_spaces).to eql(2)
174
+ expect(@bf.check).to be true
175
+
176
+ @bf.delete_blob(3)
177
+ expect(@bf.total_entries).to eql(0)
178
+ expect(@bf.total_spaces).to eql(0)
179
+ expect(@bf.check).to be true
180
+ end
181
+
182
+ it 'should not allow erase when open' do
183
+ expect{ capture_io{ @bf.erase } }.to raise_error(PEROBS::FatalError)
184
+ end
185
+
186
+ it 'should support erasing the file' do
187
+ @bf.close
188
+ @bf.erase
189
+ @bf.open
190
+ expect(@bf.total_entries).to eql(0)
191
+ expect(@bf.total_spaces).to eql(0)
192
+ expect(@bf.check).to be true
193
+ @bf.store_blob(1,'XXXXXXXX')
194
+ expect(@bf.total_entries).to eql(1)
195
+ expect(@bf.check).to be true
196
+ end
197
+
198
+ end
199
+
@@ -27,22 +27,30 @@ require 'fileutils'
27
27
 
28
28
  require 'spec_helper'
29
29
  require 'perobs/FlatFileDB'
30
+ require 'perobs/Store'
31
+
32
+ class FlatFileDB_O < PEROBS::Object
33
+
34
+ po_attr :a, :b, :c
35
+
36
+ def initialize(store)
37
+ super
38
+ attr_init(:a, 'foo')
39
+ attr_init(:b, 42)
40
+ attr_init(:c, false)
41
+ end
42
+
43
+ end
30
44
 
31
45
  describe PEROBS::FlatFileDB do
32
46
 
33
- before(:all) do
34
- @db_dir = generate_db_name('FlatFileDB')
47
+ before(:each) do
48
+ @db_dir = generate_db_name(__FILE__)
35
49
  FileUtils.mkdir_p(@db_dir)
36
- @db = PEROBS::FlatFileDB.new(@db_dir)
37
- @db.open
50
+ @store = PEROBS::Store.new(@db_dir, :engine => PEROBS::FlatFileDB)
38
51
  end
39
52
 
40
53
  after(:each) do
41
- @db.check_db
42
- end
43
-
44
- after(:all) do
45
- @db.close
46
54
  FileUtils.rm_rf(@db_dir)
47
55
  end
48
56
 
@@ -57,5 +65,51 @@ describe PEROBS::FlatFileDB do
57
65
  expect { db2.open }.to raise_error(PEROBS::FatalError)
58
66
  end
59
67
 
68
+ it 'should do a version upgrade' do
69
+ # Close the store
70
+ @store['o'] = @store.new(FlatFileDB_O)
71
+ @store.exit
72
+
73
+ # Manually downgrade the version file to version 1
74
+ version_file = File.join(@db_dir, 'version')
75
+ File.write(version_file, '1')
76
+
77
+ # Open the store again
78
+ store = PEROBS::Store.new(@db_dir, :engine => PEROBS::FlatFileDB)
79
+ expect(File.read(version_file).to_i).to eql(PEROBS::FlatFileDB::VERSION)
80
+ expect(store['o'].b).to eql(42)
81
+ end
82
+
83
+ it 'should refuse a version downgrade' do
84
+ # Close the store
85
+ @store.exit
86
+
87
+ # Manually downgrade the version file to version 1
88
+ version_file = File.join(@db_dir, 'version')
89
+ File.write(version_file, '1000000')
90
+
91
+ # Open the store again
92
+ expect { PEROBS::Store.new(@db_dir, :engine => PEROBS::FlatFileDB) }.to raise_error(PEROBS::FatalError)
93
+ end
94
+
95
+ it 'should recover from a lost index file' do
96
+ @store['o'] = @store.new(FlatFileDB_O)
97
+ @store.exit
98
+
99
+ File.delete(File.join(@db_dir, 'index.blobs'))
100
+ store = PEROBS::Store.new(@db_dir, :engine => PEROBS::FlatFileDB)
101
+ expect(store['o'].b).to eql(42)
102
+ end
103
+
104
+ it 'should repair a damaged index file' do
105
+ @store['o'] = @store.new(FlatFileDB_O)
106
+ @store.exit
107
+
108
+ File.write(File.join(@db_dir, 'index.blobs'), '*' * 500)
109
+ store = PEROBS::Store.new(@db_dir, :engine => PEROBS::FlatFileDB)
110
+ store.check(true)
111
+ expect(store['o'].b).to eql(42)
112
+ end
113
+
60
114
  end
61
115