perobs 2.5.0 → 3.0.0

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