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.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +113 -0
- data/Rakefile +22 -0
- data/lib/perobs/Array.rb +173 -0
- data/lib/perobs/BlockDB.rb +242 -0
- data/lib/perobs/Cache.rb +201 -0
- data/lib/perobs/DataBase.rb +115 -0
- data/lib/perobs/FileSystemDB.rb +171 -0
- data/lib/perobs/Hash.rb +175 -0
- data/lib/perobs/HashedBlocksDB.rb +153 -0
- data/lib/perobs/Object.rb +189 -0
- data/lib/perobs/ObjectBase.rb +159 -0
- data/lib/perobs/Store.rb +290 -0
- data/lib/perobs/version.rb +4 -0
- data/lib/perobs.rb +29 -0
- data/perobs.gemspec +23 -0
- data/spec/Array_spec.rb +94 -0
- data/spec/FileSystemDB_spec.rb +107 -0
- data/spec/Hash_spec.rb +96 -0
- data/spec/Object_spec.rb +108 -0
- data/spec/Store_spec.rb +412 -0
- data/spec/perobs_spec.rb +155 -0
- data/tasks/changelog.rake +169 -0
- data/tasks/gem.rake +50 -0
- data/tasks/rdoc.rake +14 -0
- data/tasks/test.rake +7 -0
- metadata +121 -0
data/lib/perobs/Store.rb
ADDED
@@ -0,0 +1,290 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# = Store.rb -- Persistent Ruby Object Store
|
4
|
+
#
|
5
|
+
# Copyright (c) 2015 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/Cache'
|
29
|
+
require 'perobs/FileSystemDB'
|
30
|
+
require 'perobs/HashedBlocksDB'
|
31
|
+
require 'perobs/Object'
|
32
|
+
require 'perobs/Hash'
|
33
|
+
require 'perobs/Array'
|
34
|
+
|
35
|
+
# PErsistent Ruby OBject Store
|
36
|
+
module PEROBS
|
37
|
+
|
38
|
+
# PEROBS::Store is a persistent storage system for Ruby objects. Regular
|
39
|
+
# Ruby objects are transparently stored in a back-end storage and retrieved
|
40
|
+
# when needed. It features a garbage collector that removes all objects that
|
41
|
+
# are no longer in use. A build-in cache keeps access latencies to recently
|
42
|
+
# used objects low and lazily flushes modified objects into the persistend
|
43
|
+
# back-end. Currently only filesystems are supported but it can easily be
|
44
|
+
# extended to any key/value database.
|
45
|
+
#
|
46
|
+
# Persistent objects must be created by deriving your class from
|
47
|
+
# PEROBS::Object. Only instance variables that are declared via
|
48
|
+
# po_attr will be persistent. It is recommended that references to other
|
49
|
+
# objects are all going to persistent objects again.
|
50
|
+
class Store
|
51
|
+
|
52
|
+
attr_reader :db, :cache
|
53
|
+
|
54
|
+
# Create a new Store.
|
55
|
+
# @param data_base [String] the name of the database
|
56
|
+
# @param options [Hash] various options to affect the operation of the
|
57
|
+
# database. Currently the following options are supported:
|
58
|
+
# :engine : The class that provides the back-end storage
|
59
|
+
# engine. By default HashedBlocksDB is used. A user
|
60
|
+
# can provide it's own storage engine that must
|
61
|
+
# conform to the same API exposed by HashedBlocksDB.
|
62
|
+
# :cache_bits : the number of bits used for cache indexing. The
|
63
|
+
# cache will hold 2 to the power of bits number of
|
64
|
+
# objects. We have separate caches for reading and
|
65
|
+
# writing. The default value is 16. It probably makes
|
66
|
+
# little sense to use much larger numbers than that.
|
67
|
+
# :serializer : select the format used to serialize the data. There
|
68
|
+
# are 3 different options:
|
69
|
+
# :marshal : Native Ruby serializer. Fastest option
|
70
|
+
# that can handle most Ruby data types. Big
|
71
|
+
# disadvantate is the stability of the format. Data
|
72
|
+
# written with one Ruby version may not be readable
|
73
|
+
# with another version.
|
74
|
+
# :json : About half as fast as marshal, but the
|
75
|
+
# format is rock solid and portable between
|
76
|
+
# languages. It only supports basic Ruby data types
|
77
|
+
# like String, Fixnum, Float, Array, Hash. This is
|
78
|
+
# the default option.
|
79
|
+
# :yaml : Can also handle most Ruby data types and is
|
80
|
+
# portable between Ruby versions (1.9 and later).
|
81
|
+
# Unfortunately, it is 10x slower than marshal.
|
82
|
+
def initialize(data_base, options = {})
|
83
|
+
# Create a backing store handler
|
84
|
+
@db = (options[:engine] || HashedBlocksDB).new(data_base, options)
|
85
|
+
|
86
|
+
# The Cache reduces read and write latencies by keeping a subset of the
|
87
|
+
# objects in memory.
|
88
|
+
@cache = Cache.new(options[:cache_bits] || 16)
|
89
|
+
|
90
|
+
# The named (global) objects IDs hashed by their name
|
91
|
+
unless (@root_objects = object_by_id(0))
|
92
|
+
@root_objects = Hash.new(self)
|
93
|
+
# The root object hash always has the object ID 0.
|
94
|
+
@root_objects._change_id(0)
|
95
|
+
# The ID change removes it from the write cache. We need to add it
|
96
|
+
# again.
|
97
|
+
@cache.cache_write(@root_objects)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Store the provided object under the given name. Use this to make the
|
102
|
+
# object a root or top-level object (think global variable). Each store
|
103
|
+
# should have at least one root object. Objects that are not directly or
|
104
|
+
# indirectly reachable via any of the root objects are no longer
|
105
|
+
# accessible and will be garbage collected.
|
106
|
+
# @param name [Symbol] The name to use.
|
107
|
+
# @param obj [PEROBS::Object] The object to store
|
108
|
+
# @return [PEROBS::Object] The stored object.
|
109
|
+
def []=(name, obj)
|
110
|
+
# If the passed object is nil, we delete the entry if it exists.
|
111
|
+
if obj.nil?
|
112
|
+
@root_objects.delete(name)
|
113
|
+
return nil
|
114
|
+
end
|
115
|
+
|
116
|
+
# We only allow derivatives of PEROBS::Object to be stored in the
|
117
|
+
# store.
|
118
|
+
unless obj.is_a?(ObjectBase)
|
119
|
+
raise ArgumentError, "Object must be of class PEROBS::Object but "
|
120
|
+
"is of class #{obj.class}"
|
121
|
+
end
|
122
|
+
|
123
|
+
unless obj.store == self
|
124
|
+
raise ArgumentError, 'The object does not belong to this store.'
|
125
|
+
end
|
126
|
+
|
127
|
+
# Store the name and mark the name list as modified.
|
128
|
+
@root_objects[name] = obj._id
|
129
|
+
# Add the object to the in-memory storage list.
|
130
|
+
@cache.cache_write(obj)
|
131
|
+
|
132
|
+
obj
|
133
|
+
end
|
134
|
+
|
135
|
+
# Return the object with the provided name.
|
136
|
+
# @param name [Symbol] A Symbol specifies the name of the object to be
|
137
|
+
# returned.
|
138
|
+
# @return The requested object or nil if it doesn't exist.
|
139
|
+
def [](name)
|
140
|
+
# Return nil if there is no object with that name.
|
141
|
+
return nil unless (id = @root_objects[name])
|
142
|
+
|
143
|
+
object_by_id(id)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Flush out all modified objects to disk and shrink the in-memory list if
|
147
|
+
# needed.
|
148
|
+
def sync
|
149
|
+
if @cache.in_transaction?
|
150
|
+
raise RuntimeError, 'You cannot call sync during a transaction'
|
151
|
+
end
|
152
|
+
@cache.flush
|
153
|
+
end
|
154
|
+
|
155
|
+
# Discard all objects that are not somehow connected to the root objects
|
156
|
+
# from the back-end storage. The garbage collector is not invoked
|
157
|
+
# automatically. Depending on your usage pattern, you need to call this
|
158
|
+
# method periodically.
|
159
|
+
def gc
|
160
|
+
sync
|
161
|
+
mark
|
162
|
+
sweep
|
163
|
+
end
|
164
|
+
|
165
|
+
# Return the object with the provided ID. This method is not part of the
|
166
|
+
# public API and should never be called by outside users. It's purely
|
167
|
+
# intended for internal use.
|
168
|
+
def object_by_id(id)
|
169
|
+
if (obj = @cache.object_by_id(id))
|
170
|
+
# We have the object in memory so we can just return it.
|
171
|
+
return obj
|
172
|
+
else
|
173
|
+
# We don't have the object in memory. Let's find it in the storage.
|
174
|
+
if @db.include?(id)
|
175
|
+
# Great, object found. Read it into memory and return it.
|
176
|
+
obj = ObjectBase::read(self, id)
|
177
|
+
# Add the object to the in-memory storage list.
|
178
|
+
@cache.cache_read(obj)
|
179
|
+
|
180
|
+
return obj
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# The requested object does not exist. Return nil.
|
185
|
+
nil
|
186
|
+
end
|
187
|
+
|
188
|
+
# This method can be used to check the database and optionally repair it.
|
189
|
+
# The repair is a pure structural repair. It cannot ensure that the stored
|
190
|
+
# data is still correct. E. g. if a reference to a non-existing or
|
191
|
+
# unreadable object is found, the reference will simply be deleted.
|
192
|
+
# @param repair [TrueClass/FalseClass] true if a repair attempt should be
|
193
|
+
# made.
|
194
|
+
def check(repair = true)
|
195
|
+
@db.clear_marks
|
196
|
+
# A buffer to hold a working set of object IDs.
|
197
|
+
stack = []
|
198
|
+
# First we check the top-level objects. They are only added to the
|
199
|
+
# working set if they are OK.
|
200
|
+
@root_objects.each do |name, id|
|
201
|
+
unless @db.check(id, repair)
|
202
|
+
stack << id
|
203
|
+
end
|
204
|
+
end
|
205
|
+
if repair
|
206
|
+
# Delete any top-level object that is defective.
|
207
|
+
stack.each { |id| @root_objects.delete(id) }
|
208
|
+
# The remaining top-level objects are the initial working set.
|
209
|
+
stack = @root_objects.values
|
210
|
+
else
|
211
|
+
# The initial working set must only be OK objects.
|
212
|
+
stack = @root_objects.values - stack
|
213
|
+
end
|
214
|
+
stack.each { |id| @db.mark(id) }
|
215
|
+
|
216
|
+
while !stack.empty?
|
217
|
+
id = stack.pop
|
218
|
+
(obj = object_by_id(id))._referenced_object_ids.each do |id|
|
219
|
+
# Add all found references that have passed the check to the working
|
220
|
+
# list for the next iterations.
|
221
|
+
if @db.check(id, repair)
|
222
|
+
unless @db.is_marked?(id)
|
223
|
+
stack << id
|
224
|
+
@db.mark(id)
|
225
|
+
end
|
226
|
+
elsif repair
|
227
|
+
# Remove references to bad objects.
|
228
|
+
obj._delete_reference_to_id(id)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
# This method will execute the provided block as an atomic transaction
|
235
|
+
# regarding the manipulation of all objects associated with this Store. In
|
236
|
+
# case the execution of the block generates an exception, the transaction
|
237
|
+
# is aborted and all PEROBS objects are restored to the state at the
|
238
|
+
# beginning of the transaction. The exception is passed on to the
|
239
|
+
# enclosing scope, so you probably want to handle it accordingly.
|
240
|
+
def transaction
|
241
|
+
@cache.begin_transaction
|
242
|
+
begin
|
243
|
+
yield if block_given?
|
244
|
+
rescue => e
|
245
|
+
@cache.abort_transaction
|
246
|
+
raise e
|
247
|
+
end
|
248
|
+
@cache.end_transaction
|
249
|
+
end
|
250
|
+
|
251
|
+
# Calls the given block once for each object, passing that object as a
|
252
|
+
# parameter.
|
253
|
+
def each
|
254
|
+
@db.clear_marks
|
255
|
+
# Start with the object 0 and the indexes of the root objects. Push them
|
256
|
+
# onto the work stack.
|
257
|
+
stack = [ 0 ] + @root_objects.values
|
258
|
+
stack.each { |id| @db.mark(id) }
|
259
|
+
while !stack.empty?
|
260
|
+
# Get an object index from the stack.
|
261
|
+
obj = object_by_id(id = stack.pop)
|
262
|
+
yield(obj) if block_given?
|
263
|
+
obj._referenced_object_ids.each do |id|
|
264
|
+
unless @db.is_marked?(id)
|
265
|
+
@db.mark(id)
|
266
|
+
stack << id
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
private
|
273
|
+
|
274
|
+
# Mark phase of a mark-and-sweep garbage collector. It will mark all
|
275
|
+
# objects that are reachable from the root objects.
|
276
|
+
def mark
|
277
|
+
each
|
278
|
+
end
|
279
|
+
|
280
|
+
# Sweep phase of a mark-and-sweep garbage collector. It will remove all
|
281
|
+
# unmarked objects from the store.
|
282
|
+
def sweep
|
283
|
+
@db.delete_unmarked_objects
|
284
|
+
@cache.reset
|
285
|
+
end
|
286
|
+
|
287
|
+
end
|
288
|
+
|
289
|
+
end
|
290
|
+
|
data/lib/perobs.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# = perobs.rb -- Persistent Ruby Object Store
|
4
|
+
#
|
5
|
+
# Copyright (c) 2015 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/version"
|
29
|
+
require 'perobs/Store'
|
data/perobs.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'perobs/version'
|
5
|
+
|
6
|
+
GEM_SPEC = Gem::Specification.new do |spec|
|
7
|
+
spec.name = "perobs"
|
8
|
+
spec.version = PEROBS::VERSION
|
9
|
+
spec.authors = ["Chris Schlaeger"]
|
10
|
+
spec.email = ["chris@linux.com"]
|
11
|
+
spec.summary = %q{Persistent Ruby Object Store}
|
12
|
+
spec.description = %q{Library to provide a persistent object store}
|
13
|
+
spec.homepage = "https://github.com/scrapper/perobs"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_development_dependency 'bundler', '~> 1.6'
|
21
|
+
spec.add_development_dependency 'yard', '~>0.8.7'
|
22
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
23
|
+
end
|
data/spec/Array_spec.rb
ADDED
@@ -0,0 +1,94 @@
|
|
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
|
+
|
31
|
+
require 'perobs'
|
32
|
+
|
33
|
+
class PO < PEROBS::Object
|
34
|
+
|
35
|
+
po_attr :name
|
36
|
+
|
37
|
+
def initialize(store, name = nil)
|
38
|
+
super(store)
|
39
|
+
set(:name, name)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
describe PEROBS::Array do
|
45
|
+
|
46
|
+
before(:all) do
|
47
|
+
@db_name = 'test_db'
|
48
|
+
FileUtils.rm_rf(@db_name)
|
49
|
+
end
|
50
|
+
|
51
|
+
after(:each) do
|
52
|
+
FileUtils.rm_rf(@db_name)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should store simple objects persistently' do
|
56
|
+
store = PEROBS::Store.new(@db_name)
|
57
|
+
store['a'] = a = PEROBS::Array.new(store)
|
58
|
+
a[0] = 'A'
|
59
|
+
a[1] = 'B'
|
60
|
+
a[2] = po = PO.new(store)
|
61
|
+
po.name = 'foobar'
|
62
|
+
|
63
|
+
a[0].should == 'A'
|
64
|
+
a[1].should == 'B'
|
65
|
+
a[2].name.should == 'foobar'
|
66
|
+
store.sync
|
67
|
+
|
68
|
+
store = PEROBS::Store.new(@db_name)
|
69
|
+
a = store['a']
|
70
|
+
a[0].should == 'A'
|
71
|
+
a[1].should == 'B'
|
72
|
+
a[2].name.should == 'foobar'
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'should have an each method to iterate' do
|
76
|
+
store = PEROBS::Store.new(@db_name)
|
77
|
+
store['a'] = a = PEROBS::Array.new(store)
|
78
|
+
a[0] = 'A'
|
79
|
+
a[1] = 'B'
|
80
|
+
a[2] = 'C'
|
81
|
+
vs = ''
|
82
|
+
a.each { |v| vs << v }
|
83
|
+
vs.should == 'ABC'
|
84
|
+
store.sync
|
85
|
+
|
86
|
+
store = PEROBS::Store.new(@db_name)
|
87
|
+
a = store['a']
|
88
|
+
vs = ''
|
89
|
+
a[3] = PO.new(store, 'D')
|
90
|
+
a.each { |v| vs << (v.is_a?(String) ? v : v.name) }
|
91
|
+
vs.should == 'ABCD'
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
@@ -0,0 +1,107 @@
|
|
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
|
+
|
31
|
+
require 'perobs/FileSystemDB'
|
32
|
+
|
33
|
+
class TempStruct < Struct.new(:first, :second, :third)
|
34
|
+
end
|
35
|
+
|
36
|
+
describe PEROBS::FileSystemDB do
|
37
|
+
|
38
|
+
before(:all) do
|
39
|
+
FileUtils.rm_rf('fs_test')
|
40
|
+
end
|
41
|
+
|
42
|
+
after(:each) do
|
43
|
+
FileUtils.rm_rf('fs_test')
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should create database' do
|
47
|
+
@db = PEROBS::FileSystemDB.new('fs_test')
|
48
|
+
Dir.exists?('fs_test').should be_true
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should support object insertion and retrieval' do
|
52
|
+
@db = PEROBS::FileSystemDB.new('fs_test')
|
53
|
+
@db.include?(0).should be_false
|
54
|
+
h = {
|
55
|
+
'String' => 'What god has wrought',
|
56
|
+
'Fixnum' => 42,
|
57
|
+
'Float' => 3.14,
|
58
|
+
'True' => true,
|
59
|
+
'False' => false,
|
60
|
+
'nil' => nil,
|
61
|
+
'Array' => [ 0, 1, 2, 3 ]
|
62
|
+
}
|
63
|
+
@db.put_object(h, 0)
|
64
|
+
@db.include?(0).should be_true
|
65
|
+
@db.check(0, false).should be_true
|
66
|
+
@db.get_object(0).should == h
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should support most Ruby objects types' do
|
70
|
+
[ :marshal, :yaml ].each do |serializer|
|
71
|
+
@db = PEROBS::FileSystemDB.new('fs_test', { :serializer => serializer })
|
72
|
+
@db.include?(0).should be_false
|
73
|
+
h = {
|
74
|
+
'String' => 'What god has wrought',
|
75
|
+
'Fixnum' => 42,
|
76
|
+
'Float' => 3.14,
|
77
|
+
'True' => true,
|
78
|
+
'False' => false,
|
79
|
+
'nil' => nil,
|
80
|
+
'Array' => [ 0, 1, 2, 3 ],
|
81
|
+
'Time' => Time.parse('2015-05-14-13:52:17'),
|
82
|
+
'Struct' => TempStruct.new("Where's", 'your', 'towel?')
|
83
|
+
}
|
84
|
+
@db.put_object(h, 0)
|
85
|
+
@db.include?(0).should be_true
|
86
|
+
@db.check(0, false).should be_true
|
87
|
+
@db.get_object(0).should == h
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'should mark objects and detect markings' do
|
92
|
+
@db = PEROBS::FileSystemDB.new('fs_test')
|
93
|
+
h = { 'a' => 'z' }
|
94
|
+
@db.put_object(h, 1)
|
95
|
+
@db.put_object(h, 2)
|
96
|
+
@db.clear_marks
|
97
|
+
@db.is_marked?(1).should be_false
|
98
|
+
@db.mark(1)
|
99
|
+
@db.is_marked?(1).should be_true
|
100
|
+
|
101
|
+
@db.include?(2).should be_true
|
102
|
+
@db.delete_unmarked_objects
|
103
|
+
@db.include?(1).should be_true
|
104
|
+
@db.include?(2).should be_false
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
data/spec/Hash_spec.rb
ADDED
@@ -0,0 +1,96 @@
|
|
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
|
+
|
31
|
+
require 'perobs'
|
32
|
+
|
33
|
+
|
34
|
+
class PO < PEROBS::Object
|
35
|
+
|
36
|
+
po_attr :name
|
37
|
+
|
38
|
+
def initialize(store, name = nil)
|
39
|
+
super(store)
|
40
|
+
@name = name
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
describe PEROBS::Hash do
|
46
|
+
|
47
|
+
before(:all) do
|
48
|
+
@db_name = 'test_db'
|
49
|
+
FileUtils.rm_rf(@db_name)
|
50
|
+
end
|
51
|
+
|
52
|
+
after(:each) do
|
53
|
+
FileUtils.rm_rf(@db_name)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'should store simple objects persistently' do
|
57
|
+
store = PEROBS::Store.new(@db_name)
|
58
|
+
store['h'] = h = PEROBS::Hash.new(store)
|
59
|
+
h['a'] = 'A'
|
60
|
+
h['b'] = 'B'
|
61
|
+
h['po'] = po = PO.new(store)
|
62
|
+
po.name = 'foobar'
|
63
|
+
h['b'] = 'B'
|
64
|
+
|
65
|
+
h['a'].should == 'A'
|
66
|
+
h['b'].should == 'B'
|
67
|
+
store.sync
|
68
|
+
|
69
|
+
store = PEROBS::Store.new(@db_name)
|
70
|
+
h = store['h']
|
71
|
+
h['a'].should == 'A'
|
72
|
+
h['b'].should == 'B'
|
73
|
+
h['po'].name.should == 'foobar'
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'should have an each method to iterate' do
|
77
|
+
store = PEROBS::Store.new(@db_name)
|
78
|
+
store['h'] = h = PEROBS::Hash.new(store)
|
79
|
+
h['a'] = 'A'
|
80
|
+
h['b'] = 'B'
|
81
|
+
h['c'] = 'C'
|
82
|
+
vs = []
|
83
|
+
h.each { |k, v| vs << k + v }
|
84
|
+
vs.sort.join.should == 'aAbBcC'
|
85
|
+
|
86
|
+
store = PEROBS::Store.new(@db_name)
|
87
|
+
store['h'] = h = PEROBS::Hash.new(store)
|
88
|
+
h['a'] = PO.new(store, 'A')
|
89
|
+
h['b'] = PO.new(store, 'B')
|
90
|
+
h['c'] = PO.new(store, 'C')
|
91
|
+
vs = []
|
92
|
+
h.each { |k, v| vs << k + v.name }
|
93
|
+
vs.sort.join.should == 'aAbBcC'
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|