perobs 2.5.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/perobs/Array.rb +1 -1
- data/lib/perobs/BTree.rb +233 -0
- data/lib/perobs/BTreeDB.rb +2 -0
- data/lib/perobs/BTreeNode.rb +706 -0
- data/lib/perobs/BTreeNodeCache.rb +107 -0
- data/lib/perobs/BTreeNodeLink.rb +141 -0
- data/lib/perobs/EquiBlobsFile.rb +570 -0
- data/lib/perobs/FlatFile.rb +179 -78
- data/lib/perobs/FlatFileBlobHeader.rb +92 -17
- data/lib/perobs/FlatFileDB.rb +16 -7
- data/lib/perobs/LockFile.rb +181 -0
- data/lib/perobs/Object.rb +2 -1
- data/lib/perobs/SpaceTree.rb +181 -0
- data/lib/perobs/SpaceTreeNode.rb +672 -0
- data/lib/perobs/SpaceTreeNodeCache.rb +76 -0
- data/lib/perobs/SpaceTreeNodeLink.rb +103 -0
- data/lib/perobs/Store.rb +27 -13
- data/lib/perobs/version.rb +1 -1
- data/test/BTree_spec.rb +128 -0
- data/test/EquiBlobsFile_spec.rb +199 -0
- data/test/FlatFileDB_spec.rb +63 -9
- data/test/LockFile_spec.rb +133 -0
- data/test/SpaceTree_spec.rb +245 -0
- data/test/Store_spec.rb +3 -0
- data/test/spec_helper.rb +13 -0
- metadata +21 -13
- data/lib/perobs/FixedSizeBlobFile.rb +0 -193
- data/lib/perobs/FreeSpaceManager.rb +0 -204
- data/lib/perobs/IndexTree.rb +0 -145
- data/lib/perobs/IndexTreeNode.rb +0 -316
- data/test/FixedSizeBlobFile_spec.rb +0 -91
- data/test/FreeSpaceManager_spec.rb +0 -91
- data/test/IndexTree_spec.rb +0 -118
@@ -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
|
-
|
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.
|
521
|
-
"
|
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.
|
525
|
-
"non-existing object #{id}:\n" +
|
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
|
data/lib/perobs/version.rb
CHANGED
data/test/BTree_spec.rb
ADDED
@@ -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
|
+
|
data/test/FlatFileDB_spec.rb
CHANGED
@@ -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(:
|
34
|
-
@db_dir = generate_db_name(
|
47
|
+
before(:each) do
|
48
|
+
@db_dir = generate_db_name(__FILE__)
|
35
49
|
FileUtils.mkdir_p(@db_dir)
|
36
|
-
@
|
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
|
|