hammerspace-fork 0.1.5.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/.chef/cookbooks/hammerspace-development/attributes/default.rb +7 -0
- data/.chef/cookbooks/hammerspace-development/attributes/essential.rb +6 -0
- data/.chef/cookbooks/hammerspace-development/attributes/sparkey.rb +7 -0
- data/.chef/cookbooks/hammerspace-development/recipes/default.rb +32 -0
- data/.chef/cookbooks/hammerspace-development/recipes/essential.rb +9 -0
- data/.chef/cookbooks/hammerspace-development/recipes/ruby.rb +21 -0
- data/.chef/cookbooks/hammerspace-development/recipes/sparkey.rb +56 -0
- data/.chef/cookbooks/hammerspace-development/templates/default/.bash_profile.erb +2 -0
- data/.chef/roles/hammerspace-development.rb +6 -0
- data/.gitignore +8 -0
- data/CHANGELOG.md +30 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +523 -0
- data/Vagrantfile +30 -0
- data/hammerspace-fork.gemspec +21 -0
- data/lib/hammerspace.rb +12 -0
- data/lib/hammerspace/backend.rb +106 -0
- data/lib/hammerspace/backend/sparkey.rb +319 -0
- data/lib/hammerspace/hash.rb +62 -0
- data/lib/hammerspace/hash_methods.rb +234 -0
- data/lib/hammerspace/version.rb +3 -0
- data/script/write_concurrency_test.rb +36 -0
- data/spec/features/hash_spec.rb +1487 -0
- data/spec/lib/hammerspace/backend/sparkey_spec.rb +191 -0
- data/spec/lib/hammerspace/hash_spec.rb +143 -0
- data/spec/lib/hammerspace_spec.rb +27 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/support/sparkey_directory_helper.rb +26 -0
- data/spec/support/write_concurrency_test.rb +38 -0
- metadata +96 -0
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Hammerspace
|
4
|
+
|
5
|
+
# "Frontend" class
|
6
|
+
#
|
7
|
+
# All hammerspace functionality is exposed through this class's interface.
|
8
|
+
# Responsible for setting up the backend and delegating methods to the
|
9
|
+
# backend. Also handles default values. This functionality is designed to be
|
10
|
+
# consistent across backends; backends cannot be override this functionality.
|
11
|
+
class Hash
|
12
|
+
extend Forwardable
|
13
|
+
|
14
|
+
attr_reader :path
|
15
|
+
attr_reader :options
|
16
|
+
attr_reader :backend
|
17
|
+
attr_reader :default_proc
|
18
|
+
|
19
|
+
def_delegators :backend, *Enumerable.instance_methods
|
20
|
+
def_delegators :backend, *HashMethods.instance_methods
|
21
|
+
def_delegators :backend, :close, :uid
|
22
|
+
|
23
|
+
DEFAULT_OPTIONS = {
|
24
|
+
:backend => Hammerspace::Backend::Sparkey
|
25
|
+
}
|
26
|
+
|
27
|
+
def initialize(path, options={}, *args, &block)
|
28
|
+
raise ArgumentError, "wrong number of arguments" if args.size > 1
|
29
|
+
|
30
|
+
@path = path
|
31
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
32
|
+
@backend = @options[:backend].new(self, @path, @options)
|
33
|
+
|
34
|
+
if block_given?
|
35
|
+
self.default_proc=(block)
|
36
|
+
raise ArgumentError, "wrong number of arguments" if args.size == 1
|
37
|
+
else
|
38
|
+
self.default=args.first
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def default(*args)
|
43
|
+
if @default_proc && args.size
|
44
|
+
@default_proc.call(self, args.first)
|
45
|
+
else
|
46
|
+
@default
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def default=(value)
|
51
|
+
@default_proc = nil
|
52
|
+
@default = value
|
53
|
+
end
|
54
|
+
|
55
|
+
def default_proc=(value)
|
56
|
+
@default = nil
|
57
|
+
@default_proc = value
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,234 @@
|
|
1
|
+
module Hammerspace
|
2
|
+
|
3
|
+
# Basic implementations of most methods supported by Ruby's hash
|
4
|
+
#
|
5
|
+
# Analogous to Enumerable. Mixed into Hammerspace::Backend::Base. A backend
|
6
|
+
# need only implement four of these methods: [], []=, delete, and each. (A
|
7
|
+
# backend should also implement close and uid, but these are not hash
|
8
|
+
# methods; they are hammerspace-specific.) However, a backend may choose to
|
9
|
+
# override some of the default implementations if the backend is able to
|
10
|
+
# implement the methods more efficiently.
|
11
|
+
module HashMethods
|
12
|
+
|
13
|
+
def ==(hash)
|
14
|
+
return false if size != hash.size
|
15
|
+
each do |key, value|
|
16
|
+
return false unless hash.has_key?(key)
|
17
|
+
return false unless hash[key] == value
|
18
|
+
end
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
def [](key)
|
23
|
+
raise NotImplementedError
|
24
|
+
end
|
25
|
+
|
26
|
+
def []=(key, value)
|
27
|
+
raise NotImplementedError
|
28
|
+
end
|
29
|
+
|
30
|
+
def assoc(key)
|
31
|
+
find { |k,v| k == key }
|
32
|
+
end
|
33
|
+
|
34
|
+
def clear
|
35
|
+
each { |key, value| delete(key) }
|
36
|
+
close # flush immediately
|
37
|
+
frontend
|
38
|
+
end
|
39
|
+
|
40
|
+
def delete(key)
|
41
|
+
raise NotImplementedError
|
42
|
+
end
|
43
|
+
|
44
|
+
def delete_if(&block)
|
45
|
+
if block_given?
|
46
|
+
reject!(&block)
|
47
|
+
frontend
|
48
|
+
else
|
49
|
+
reject!
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def each(&block)
|
54
|
+
raise NotImplementedError
|
55
|
+
end
|
56
|
+
|
57
|
+
def each_key
|
58
|
+
if block_given?
|
59
|
+
each { |key, value| yield key }
|
60
|
+
else
|
61
|
+
Enumerator.new { |y| each { |key, value| y << key } }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def each_value
|
66
|
+
if block_given?
|
67
|
+
each { |key, value| yield value }
|
68
|
+
else
|
69
|
+
Enumerator.new { |y| each { |key, value| y << value } }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def empty?
|
74
|
+
size == 0
|
75
|
+
end
|
76
|
+
|
77
|
+
def eql?(hash)
|
78
|
+
return false if size != hash.size
|
79
|
+
each do |key, value|
|
80
|
+
return false unless hash.has_key?(key)
|
81
|
+
return false unless hash[key].eql?(value)
|
82
|
+
end
|
83
|
+
true
|
84
|
+
end
|
85
|
+
|
86
|
+
def fetch(key, *args)
|
87
|
+
raise ArgumentError, "wrong number of arguments" if args.size > 1
|
88
|
+
|
89
|
+
return self[key] if has_key?(key)
|
90
|
+
|
91
|
+
if block_given?
|
92
|
+
yield key
|
93
|
+
elsif args.size == 1
|
94
|
+
args.first
|
95
|
+
else
|
96
|
+
raise KeyError, "key not found: \"#{key}\""
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def flatten(*args)
|
101
|
+
# Note: the optional level argument is supported for compatibility, but
|
102
|
+
# it will never have an effect because only string values are
|
103
|
+
# supported.
|
104
|
+
raise ArgumentError, "wrong number of arguments" if args.size > 1
|
105
|
+
|
106
|
+
each_with_object([]) do |args, array|
|
107
|
+
array << args.first
|
108
|
+
array << args.last
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def has_key?(key)
|
113
|
+
!!find { |k,v| k.eql?(key) }
|
114
|
+
end
|
115
|
+
|
116
|
+
def has_value?(value)
|
117
|
+
!!find { |k,v| v == value }
|
118
|
+
end
|
119
|
+
|
120
|
+
def keep_if(&block)
|
121
|
+
if block_given?
|
122
|
+
select!(&block)
|
123
|
+
frontend
|
124
|
+
else
|
125
|
+
select!
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def key(key)
|
130
|
+
has_key?(key) ? self[key] : nil
|
131
|
+
end
|
132
|
+
|
133
|
+
def keys
|
134
|
+
each.map { |key, value| key }
|
135
|
+
end
|
136
|
+
|
137
|
+
def merge!(hash)
|
138
|
+
hash.each do |key, value|
|
139
|
+
if block_given?
|
140
|
+
self[key] = yield key, self[key], value
|
141
|
+
else
|
142
|
+
self[key] = value
|
143
|
+
end
|
144
|
+
end
|
145
|
+
frontend
|
146
|
+
end
|
147
|
+
|
148
|
+
def rassoc(value)
|
149
|
+
find { |k,v| v == value }
|
150
|
+
end
|
151
|
+
|
152
|
+
def reject!
|
153
|
+
if block_given?
|
154
|
+
any_deleted = false
|
155
|
+
each do |key, value|
|
156
|
+
if yield key, value
|
157
|
+
any_deleted = true
|
158
|
+
delete(key)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
any_deleted ? frontend : nil
|
162
|
+
else
|
163
|
+
Enumerator.new do |y|
|
164
|
+
each { |key, value| delete(key) if y.yield(key, value) }
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def replace(hash)
|
170
|
+
clear
|
171
|
+
merge!(hash)
|
172
|
+
end
|
173
|
+
|
174
|
+
def select!
|
175
|
+
if block_given?
|
176
|
+
any_deleted = false
|
177
|
+
each do |key, value|
|
178
|
+
unless yield key, value
|
179
|
+
any_deleted = true
|
180
|
+
delete(key)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
any_deleted ? frontend : nil
|
184
|
+
else
|
185
|
+
Enumerator.new do |y|
|
186
|
+
each { |key, value| delete(key) unless y.yield(key, value) }
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def shift
|
192
|
+
items = take(1)
|
193
|
+
if items.empty?
|
194
|
+
frontend.default
|
195
|
+
else
|
196
|
+
pair = items.first
|
197
|
+
delete(pair.first)
|
198
|
+
pair
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def size
|
203
|
+
count = 0
|
204
|
+
each { |key, value| count += 1 }
|
205
|
+
count
|
206
|
+
end
|
207
|
+
|
208
|
+
def to_hash
|
209
|
+
each_with_object({}) { |args, hash| hash[args.first] = args.last }
|
210
|
+
end
|
211
|
+
|
212
|
+
def values
|
213
|
+
each.map { |key, value| value }
|
214
|
+
end
|
215
|
+
|
216
|
+
def values_at(*args)
|
217
|
+
args.map { |key| self[key] }
|
218
|
+
end
|
219
|
+
|
220
|
+
alias_method :store, :[]=
|
221
|
+
alias_method :each_pair, :each
|
222
|
+
alias_method :key?, :has_key?
|
223
|
+
|
224
|
+
# alias_method seems to conflict with Enumerable's version of these methods
|
225
|
+
def include?(key); has_key?(key); end
|
226
|
+
def member?(key); has_key?(key); end
|
227
|
+
|
228
|
+
alias_method :value?, :has_value?
|
229
|
+
alias_method :update, :merge!
|
230
|
+
alias_method :initialize_copy, :replace
|
231
|
+
alias_method :length, :size
|
232
|
+
|
233
|
+
end
|
234
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.push File.expand_path('../../lib', __FILE__)
|
3
|
+
$:.push File.expand_path('../../spec', __FILE__)
|
4
|
+
|
5
|
+
require 'trollop'
|
6
|
+
require 'hammerspace'
|
7
|
+
require 'support/write_concurrency_test'
|
8
|
+
require 'fileutils'
|
9
|
+
|
10
|
+
include WriteConcurrencyTest
|
11
|
+
|
12
|
+
opts = Trollop::options do
|
13
|
+
opt :path, 'Path to hammerspace root', :default => '/tmp'
|
14
|
+
opt :backend, 'Hammerspace backend to use', :default => 'Sparkey'
|
15
|
+
opt :concurrency, 'Number of writer processes to fork', :default => 10
|
16
|
+
opt :iterations, 'Number of times each process should write', :default => 10
|
17
|
+
opt :size, 'Number of items to write on each iteration', :default => 10
|
18
|
+
end
|
19
|
+
|
20
|
+
path = File.join(opts[:path], 'write_concurrency_test')
|
21
|
+
|
22
|
+
begin
|
23
|
+
run_write_concurrency_test(
|
24
|
+
path,
|
25
|
+
{
|
26
|
+
:backend => Hammerspace::Backend.const_get(opts[:backend])
|
27
|
+
},
|
28
|
+
opts[:concurrency],
|
29
|
+
opts[:iterations],
|
30
|
+
opts[:size])
|
31
|
+
ensure
|
32
|
+
FileUtils.rm_rf(path)
|
33
|
+
end
|
34
|
+
|
35
|
+
puts "OK"
|
36
|
+
|
@@ -0,0 +1,1487 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Hammerspace do
|
4
|
+
|
5
|
+
[Hammerspace::Backend::Sparkey].each do |backend|
|
6
|
+
describe backend do
|
7
|
+
|
8
|
+
let(:path) { HAMMERSPACE_ROOT }
|
9
|
+
let(:options) { { :backend => backend } }
|
10
|
+
|
11
|
+
before do
|
12
|
+
FileUtils.rm_rf(path, :secure => true)
|
13
|
+
end
|
14
|
+
|
15
|
+
after(:all) do
|
16
|
+
FileUtils.rm_rf(path, :secure => true)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "gets after set" do
|
20
|
+
hash = Hammerspace.new(path, options)
|
21
|
+
hash['foo'] = 'bar'
|
22
|
+
hash['foo'].should == 'bar'
|
23
|
+
hash.close
|
24
|
+
end
|
25
|
+
|
26
|
+
it "gets before set" do
|
27
|
+
hash = Hammerspace.new(path, options)
|
28
|
+
hash['foo'].should be_nil
|
29
|
+
hash.close
|
30
|
+
end
|
31
|
+
|
32
|
+
it "supports interleaved gets and sets" do
|
33
|
+
hash = Hammerspace.new(path, options)
|
34
|
+
hash['foo'] = 'bar'
|
35
|
+
hash['foo'].should == 'bar'
|
36
|
+
hash['foo'] = 'newvalue'
|
37
|
+
hash['foo'].should == 'newvalue'
|
38
|
+
hash.close
|
39
|
+
end
|
40
|
+
|
41
|
+
it "persists values after reopen" do
|
42
|
+
hash = Hammerspace.new(path, options)
|
43
|
+
hash['foo'] = 'bar'
|
44
|
+
hash.close
|
45
|
+
|
46
|
+
hash = Hammerspace.new(path, options)
|
47
|
+
hash['foo'].should == 'bar'
|
48
|
+
hash.close
|
49
|
+
end
|
50
|
+
|
51
|
+
it "allows updating after reopen" do
|
52
|
+
hash = Hammerspace.new(path, options)
|
53
|
+
hash['foo'] = 'bar'
|
54
|
+
hash.close
|
55
|
+
|
56
|
+
hash = Hammerspace.new(path, options)
|
57
|
+
hash['foo'] = 'newvalue'
|
58
|
+
hash['foo'].should == 'newvalue'
|
59
|
+
hash.close
|
60
|
+
end
|
61
|
+
|
62
|
+
it "supports multiple readers" do
|
63
|
+
hash = Hammerspace.new(path, options)
|
64
|
+
hash['foo'] = 'bar'
|
65
|
+
hash.close
|
66
|
+
|
67
|
+
reader1 = Hammerspace.new(path, options)
|
68
|
+
reader1['foo'].should == 'bar'
|
69
|
+
|
70
|
+
reader2 = Hammerspace.new(path, options)
|
71
|
+
reader2['foo'].should == 'bar'
|
72
|
+
|
73
|
+
reader1.close
|
74
|
+
reader2.close
|
75
|
+
end
|
76
|
+
|
77
|
+
it "isolates readers" do
|
78
|
+
hash = Hammerspace.new(path, options)
|
79
|
+
hash['foo'] = 'bar'
|
80
|
+
hash.close
|
81
|
+
|
82
|
+
reader1 = Hammerspace.new(path, options)
|
83
|
+
reader1['foo'].should == 'bar'
|
84
|
+
|
85
|
+
hash = Hammerspace.new(path, options)
|
86
|
+
hash['foo'] = 'newvalue'
|
87
|
+
hash.close
|
88
|
+
|
89
|
+
reader1['foo'].should == 'bar' # still 'bar'
|
90
|
+
|
91
|
+
reader2 = Hammerspace.new(path, options)
|
92
|
+
reader2['foo'].should == 'newvalue'
|
93
|
+
|
94
|
+
reader1.close
|
95
|
+
reader2.close
|
96
|
+
end
|
97
|
+
|
98
|
+
it "supports multiple writers" do
|
99
|
+
writer1 = Hammerspace.new(path, options)
|
100
|
+
writer1['foo'] = 'one'
|
101
|
+
|
102
|
+
writer2 = Hammerspace.new(path, options)
|
103
|
+
writer2['foo'] = 'two'
|
104
|
+
writer2['bar'] = 'two' # test works even without locking if this isn't here?
|
105
|
+
|
106
|
+
writer2.close
|
107
|
+
writer1.close # last write wins
|
108
|
+
|
109
|
+
hash = Hammerspace.new(path, options)
|
110
|
+
hash['foo'].should == 'one'
|
111
|
+
hash['bar'].should be_nil
|
112
|
+
hash.close
|
113
|
+
end
|
114
|
+
|
115
|
+
it "supports multiple appenders" do
|
116
|
+
hash = Hammerspace.new(path, options)
|
117
|
+
hash['foo'] = 'bar'
|
118
|
+
hash.close
|
119
|
+
|
120
|
+
writer1 = Hammerspace.new(path, options)
|
121
|
+
writer1['foo'] = 'one'
|
122
|
+
|
123
|
+
writer2 = Hammerspace.new(path, options)
|
124
|
+
writer2['foo'] = 'two'
|
125
|
+
writer2['bar'] = 'two' # test works even without locking if this isn't here?
|
126
|
+
|
127
|
+
writer2.close
|
128
|
+
writer1.close # last write wins
|
129
|
+
|
130
|
+
hash = Hammerspace.new(path, options)
|
131
|
+
hash['foo'].should == 'one'
|
132
|
+
hash['bar'].should be_nil
|
133
|
+
hash.close
|
134
|
+
end
|
135
|
+
|
136
|
+
it "handles high write concurrency" do
|
137
|
+
run_write_concurrency_test(path, options)
|
138
|
+
end
|
139
|
+
|
140
|
+
describe "#==" do
|
141
|
+
|
142
|
+
it "returns false if different sizes" do
|
143
|
+
h1 = Hammerspace.new(File.join(path, '1'), options)
|
144
|
+
h1['a'] = 'A'
|
145
|
+
h1['b'] = 'B'
|
146
|
+
|
147
|
+
h2 = Hammerspace.new(File.join(path, '2'), options)
|
148
|
+
h2['a'] = 'A'
|
149
|
+
|
150
|
+
h1.should_not == h2
|
151
|
+
|
152
|
+
h1.close
|
153
|
+
h2.close
|
154
|
+
end
|
155
|
+
|
156
|
+
it "does not consider default values" do
|
157
|
+
h1 = Hammerspace.new(File.join(path, '1'), options)
|
158
|
+
h1['a'] = 'A'
|
159
|
+
h1['b'] = 'B'
|
160
|
+
|
161
|
+
h2 = Hammerspace.new(File.join(path, '2'), options, 'B')
|
162
|
+
h2['a'] = 'A'
|
163
|
+
|
164
|
+
h1.should_not == h2
|
165
|
+
|
166
|
+
h1.close
|
167
|
+
h2.close
|
168
|
+
end
|
169
|
+
|
170
|
+
it "returns false if different keys" do
|
171
|
+
h1 = Hammerspace.new(File.join(path, '1'), options)
|
172
|
+
h1['a'] = 'A'
|
173
|
+
h1['b'] = 'B'
|
174
|
+
|
175
|
+
h2 = Hammerspace.new(File.join(path, '2'), options)
|
176
|
+
h2['a'] = 'A'
|
177
|
+
h2['B'] = 'B'
|
178
|
+
|
179
|
+
h1.should_not == h2
|
180
|
+
|
181
|
+
h1.close
|
182
|
+
h2.close
|
183
|
+
end
|
184
|
+
|
185
|
+
it "returns false if different values" do
|
186
|
+
h1 = Hammerspace.new(File.join(path, '1'), options)
|
187
|
+
h1['a'] = 'A'
|
188
|
+
h1['b'] = 'B'
|
189
|
+
|
190
|
+
h2 = Hammerspace.new(File.join(path, '2'), options)
|
191
|
+
h2['a'] = 'A'
|
192
|
+
h2['b'] = 'b'
|
193
|
+
|
194
|
+
h1.should_not == h2
|
195
|
+
|
196
|
+
h1.close
|
197
|
+
h2.close
|
198
|
+
end
|
199
|
+
|
200
|
+
it "returns true if same keys and values" do
|
201
|
+
h1 = Hammerspace.new(File.join(path, '1'), options)
|
202
|
+
h1['a'] = 'A'
|
203
|
+
h1['b'] = 'B'
|
204
|
+
|
205
|
+
h2 = Hammerspace.new(File.join(path, '2'), options)
|
206
|
+
h2['a'] = 'A'
|
207
|
+
h2['b'] = 'B'
|
208
|
+
|
209
|
+
h1.should == h2
|
210
|
+
|
211
|
+
h1.close
|
212
|
+
h2.close
|
213
|
+
end
|
214
|
+
|
215
|
+
it "works with hashes" do
|
216
|
+
hash = Hammerspace.new(File.join(path, '1'), options)
|
217
|
+
hash['a'] = 'A'
|
218
|
+
hash['b'] = 'B'
|
219
|
+
hash.should == {'a' => 'A', 'b' => 'B'}
|
220
|
+
hash.close
|
221
|
+
end
|
222
|
+
|
223
|
+
end
|
224
|
+
|
225
|
+
describe "#[]" do
|
226
|
+
|
227
|
+
it "returns value if key exists" do
|
228
|
+
hash = Hammerspace.new(path, options)
|
229
|
+
hash.default = 'default'
|
230
|
+
hash['foo'] = 'bar'
|
231
|
+
hash['foo'].should == 'bar'
|
232
|
+
hash.close
|
233
|
+
end
|
234
|
+
|
235
|
+
it "returns default value if key does not exist" do
|
236
|
+
hash = Hammerspace.new(path, options)
|
237
|
+
hash.default = 'default'
|
238
|
+
hash['foo'].should == 'default'
|
239
|
+
hash.close
|
240
|
+
end
|
241
|
+
|
242
|
+
it "supports storing the default value" do
|
243
|
+
hash = Hammerspace.new(path, options) { |h,k| h[k] = 'Go fish' }
|
244
|
+
hash['foo'].should == 'Go fish'
|
245
|
+
hash.has_key?('foo').should be_true
|
246
|
+
hash.close
|
247
|
+
end
|
248
|
+
|
249
|
+
end
|
250
|
+
|
251
|
+
describe "#[]=" do
|
252
|
+
|
253
|
+
it "handles key mutation" do
|
254
|
+
hash = Hammerspace.new(path, options)
|
255
|
+
key = 'foo'
|
256
|
+
hash[key] = 'bar'
|
257
|
+
key = 'key'
|
258
|
+
hash['foo'].should == 'bar'
|
259
|
+
hash['key'].should be_nil
|
260
|
+
hash.close
|
261
|
+
end
|
262
|
+
|
263
|
+
end
|
264
|
+
|
265
|
+
describe "#assoc" do
|
266
|
+
|
267
|
+
it "returns key value pair when key is present" do
|
268
|
+
hash = Hammerspace.new(path, options)
|
269
|
+
hash['foo'] = 'bar'
|
270
|
+
hash.assoc('foo').should == ['foo', 'bar']
|
271
|
+
hash.close
|
272
|
+
end
|
273
|
+
|
274
|
+
it "returns nil when key is not present" do
|
275
|
+
hash = Hammerspace.new(path, options)
|
276
|
+
hash['foo'] = 'bar'
|
277
|
+
hash.assoc('otherkey').should be_nil
|
278
|
+
hash.close
|
279
|
+
end
|
280
|
+
|
281
|
+
it "returns nil when empty" do
|
282
|
+
hash = Hammerspace.new(path, options)
|
283
|
+
hash.assoc('foo').should be_nil
|
284
|
+
hash.close
|
285
|
+
end
|
286
|
+
|
287
|
+
end
|
288
|
+
|
289
|
+
describe "#clear" do
|
290
|
+
|
291
|
+
it "removes all keys and values" do
|
292
|
+
hash = Hammerspace.new(path, options)
|
293
|
+
hash['foo'] = 'bar'
|
294
|
+
hash.close
|
295
|
+
|
296
|
+
hash = Hammerspace.new(path, options)
|
297
|
+
hash.clear
|
298
|
+
hash['foo'].should be_nil
|
299
|
+
hash.size.should == 0
|
300
|
+
hash.close
|
301
|
+
end
|
302
|
+
|
303
|
+
it "removes unflushed keys and values" do
|
304
|
+
hash = Hammerspace.new(path, options)
|
305
|
+
hash['foo'] = 'bar'
|
306
|
+
hash.clear
|
307
|
+
hash['foo'].should be_nil
|
308
|
+
hash.size.should == 0
|
309
|
+
hash.close
|
310
|
+
end
|
311
|
+
|
312
|
+
it "returns the hash" do
|
313
|
+
hash = Hammerspace.new(path, options)
|
314
|
+
hash.clear.should == hash
|
315
|
+
hash.close
|
316
|
+
end
|
317
|
+
|
318
|
+
end
|
319
|
+
|
320
|
+
describe "#delete" do
|
321
|
+
|
322
|
+
it "deletes" do
|
323
|
+
hash = Hammerspace.new(path, options)
|
324
|
+
hash['foo'] = 'bar'
|
325
|
+
hash.delete('foo')
|
326
|
+
hash.has_key?('foo').should be_false
|
327
|
+
hash['foo'].should be_nil
|
328
|
+
hash.close
|
329
|
+
end
|
330
|
+
|
331
|
+
end
|
332
|
+
|
333
|
+
describe "#delete_if" do
|
334
|
+
|
335
|
+
let(:hash) do
|
336
|
+
h = Hammerspace.new(path, options)
|
337
|
+
h['a'] = 'A'
|
338
|
+
h['b'] = 'B'
|
339
|
+
h
|
340
|
+
end
|
341
|
+
|
342
|
+
context "with block" do
|
343
|
+
|
344
|
+
it "deletes when true" do
|
345
|
+
hash.delete_if { |key,value| key == 'a' }
|
346
|
+
hash.has_key?('a').should be_false
|
347
|
+
hash['a'].should be_nil
|
348
|
+
hash.has_key?('b').should be_true
|
349
|
+
hash['b'].should == 'B'
|
350
|
+
hash.close
|
351
|
+
end
|
352
|
+
|
353
|
+
it "returns the hash" do
|
354
|
+
hash.delete_if { |key,value| true }.should == hash
|
355
|
+
hash.close
|
356
|
+
end
|
357
|
+
|
358
|
+
end
|
359
|
+
|
360
|
+
context "with enumerator" do
|
361
|
+
|
362
|
+
it "deletes when true" do
|
363
|
+
hash.delete_if.each { |key,value| key == 'a' }
|
364
|
+
hash.has_key?('a').should be_false
|
365
|
+
hash['a'].should be_nil
|
366
|
+
hash.has_key?('b').should be_true
|
367
|
+
hash['b'].should == 'B'
|
368
|
+
hash.close
|
369
|
+
end
|
370
|
+
|
371
|
+
end
|
372
|
+
|
373
|
+
end
|
374
|
+
|
375
|
+
describe "#each" do
|
376
|
+
|
377
|
+
let(:keys) { [] }
|
378
|
+
let(:values) { [] }
|
379
|
+
let(:hash) do
|
380
|
+
h = Hammerspace.new(path, options)
|
381
|
+
h['a'] = 'A'
|
382
|
+
h['b'] = 'B'
|
383
|
+
h
|
384
|
+
end
|
385
|
+
|
386
|
+
context "with block" do
|
387
|
+
|
388
|
+
it "allows iteration" do
|
389
|
+
hash.each do |key,value|
|
390
|
+
keys << key
|
391
|
+
values << value
|
392
|
+
end
|
393
|
+
hash.close
|
394
|
+
|
395
|
+
keys.should == ['a', 'b']
|
396
|
+
values.should == ['A', 'B']
|
397
|
+
end
|
398
|
+
|
399
|
+
it "allows iteration when empty" do
|
400
|
+
iterations = 0
|
401
|
+
|
402
|
+
hash = Hammerspace.new(path, options)
|
403
|
+
hash.each { |key,value| iterations += 1 }
|
404
|
+
hash.close
|
405
|
+
|
406
|
+
iterations.should == 0
|
407
|
+
end
|
408
|
+
|
409
|
+
it "allows updating during iteration" do
|
410
|
+
hash.each do |key,value|
|
411
|
+
keys << key
|
412
|
+
values << value
|
413
|
+
hash[key] = 'C'
|
414
|
+
end
|
415
|
+
|
416
|
+
keys.should == ['a', 'b']
|
417
|
+
values.should == ['A', 'B']
|
418
|
+
|
419
|
+
hash['a'].should == 'C'
|
420
|
+
hash['b'].should == 'C'
|
421
|
+
|
422
|
+
hash.close
|
423
|
+
end
|
424
|
+
|
425
|
+
it "allows updating and reading during iteration" do
|
426
|
+
hash.each do |key,value|
|
427
|
+
keys << key
|
428
|
+
values << value
|
429
|
+
hash[key] = 'C'
|
430
|
+
hash[key].should == 'C'
|
431
|
+
end
|
432
|
+
|
433
|
+
keys.should == ['a', 'b']
|
434
|
+
values.should == ['A', 'B']
|
435
|
+
|
436
|
+
hash['a'].should == 'C'
|
437
|
+
hash['b'].should == 'C'
|
438
|
+
|
439
|
+
hash.close
|
440
|
+
end
|
441
|
+
|
442
|
+
it "isolates iterators during iteration" do
|
443
|
+
hash.each do |key,value|
|
444
|
+
hash['b'] = 'C'
|
445
|
+
keys << key
|
446
|
+
values << value
|
447
|
+
end
|
448
|
+
|
449
|
+
keys.should == ['a', 'b']
|
450
|
+
values.should == ['A', 'B']
|
451
|
+
|
452
|
+
hash['b'].should == 'C'
|
453
|
+
|
454
|
+
hash.close
|
455
|
+
end
|
456
|
+
|
457
|
+
end
|
458
|
+
|
459
|
+
context "with enumerator" do
|
460
|
+
|
461
|
+
it "allows iteration" do
|
462
|
+
hash.each.each do |key,value|
|
463
|
+
keys << key
|
464
|
+
values << value
|
465
|
+
end
|
466
|
+
hash.close
|
467
|
+
|
468
|
+
keys.should == ['a', 'b']
|
469
|
+
values.should == ['A', 'B']
|
470
|
+
end
|
471
|
+
|
472
|
+
it "allows iteration when empty" do
|
473
|
+
iterations = 0
|
474
|
+
|
475
|
+
hash = Hammerspace.new(path, options)
|
476
|
+
hash.each.each { |key,value| iterations += 1 }
|
477
|
+
hash.close
|
478
|
+
|
479
|
+
iterations.should == 0
|
480
|
+
end
|
481
|
+
|
482
|
+
it "allows updating during iteration" do
|
483
|
+
hash.each.each do |key,value|
|
484
|
+
keys << key
|
485
|
+
values << value
|
486
|
+
hash[key] = 'C'
|
487
|
+
end
|
488
|
+
|
489
|
+
keys.should == ['a', 'b']
|
490
|
+
values.should == ['A', 'B']
|
491
|
+
|
492
|
+
hash['a'].should == 'C'
|
493
|
+
hash['b'].should == 'C'
|
494
|
+
|
495
|
+
hash.close
|
496
|
+
end
|
497
|
+
|
498
|
+
it "allows updating and reading during iteration" do
|
499
|
+
hash.each.each do |key,value|
|
500
|
+
keys << key
|
501
|
+
values << value
|
502
|
+
hash[key] = 'C'
|
503
|
+
hash[key].should == 'C'
|
504
|
+
end
|
505
|
+
|
506
|
+
keys.should == ['a', 'b']
|
507
|
+
values.should == ['A', 'B']
|
508
|
+
|
509
|
+
hash['a'].should == 'C'
|
510
|
+
hash['b'].should == 'C'
|
511
|
+
|
512
|
+
hash.close
|
513
|
+
end
|
514
|
+
|
515
|
+
it "isolates iterators during iteration" do
|
516
|
+
hash.each.each do |key,value|
|
517
|
+
hash['b'] = 'C'
|
518
|
+
keys << key
|
519
|
+
values << value
|
520
|
+
end
|
521
|
+
|
522
|
+
keys.should == ['a', 'b']
|
523
|
+
values.should == ['A', 'B']
|
524
|
+
|
525
|
+
hash['b'].should == 'C'
|
526
|
+
|
527
|
+
hash.close
|
528
|
+
end
|
529
|
+
|
530
|
+
it "returns the hash" do
|
531
|
+
hash.each { |key,value| 'foo' }.should == hash
|
532
|
+
end
|
533
|
+
|
534
|
+
end
|
535
|
+
|
536
|
+
end
|
537
|
+
|
538
|
+
describe "#each_key" do
|
539
|
+
|
540
|
+
let(:keys) { [] }
|
541
|
+
let(:hash) do
|
542
|
+
h = Hammerspace.new(path, options)
|
543
|
+
h['a'] = 'A'
|
544
|
+
h['b'] = 'B'
|
545
|
+
h
|
546
|
+
end
|
547
|
+
|
548
|
+
context "with block" do
|
549
|
+
|
550
|
+
it "allows iteration" do
|
551
|
+
hash.each_key do |key|
|
552
|
+
keys << key
|
553
|
+
end
|
554
|
+
hash.close
|
555
|
+
|
556
|
+
keys.should == ['a', 'b']
|
557
|
+
end
|
558
|
+
|
559
|
+
it "allows iteration when empty" do
|
560
|
+
iterations = 0
|
561
|
+
|
562
|
+
hash = Hammerspace.new(path, options)
|
563
|
+
hash.each_key { |key| iterations += 1 }
|
564
|
+
hash.close
|
565
|
+
|
566
|
+
iterations.should == 0
|
567
|
+
end
|
568
|
+
|
569
|
+
it "allows updating during iteration" do
|
570
|
+
hash.each_key do |key|
|
571
|
+
keys << key
|
572
|
+
hash[key] = 'C'
|
573
|
+
end
|
574
|
+
|
575
|
+
keys.should == ['a', 'b']
|
576
|
+
|
577
|
+
hash['a'].should == 'C'
|
578
|
+
hash['b'].should == 'C'
|
579
|
+
|
580
|
+
hash.close
|
581
|
+
end
|
582
|
+
|
583
|
+
it "allows updating and reading during iteration" do
|
584
|
+
hash.each_key do |key|
|
585
|
+
keys << key
|
586
|
+
hash[key] = 'C'
|
587
|
+
hash[key].should == 'C'
|
588
|
+
end
|
589
|
+
|
590
|
+
keys.should == ['a', 'b']
|
591
|
+
|
592
|
+
hash['a'].should == 'C'
|
593
|
+
hash['b'].should == 'C'
|
594
|
+
|
595
|
+
hash.close
|
596
|
+
end
|
597
|
+
|
598
|
+
it "isolates iterators during iteration" do
|
599
|
+
hash.each_key do |key|
|
600
|
+
hash['b'] = 'C'
|
601
|
+
keys << key
|
602
|
+
end
|
603
|
+
|
604
|
+
keys.should == ['a', 'b']
|
605
|
+
|
606
|
+
hash['b'].should == 'C'
|
607
|
+
|
608
|
+
hash.close
|
609
|
+
end
|
610
|
+
|
611
|
+
it "returns the hash" do
|
612
|
+
hash.each_key { |key| 'foo' }.should == hash
|
613
|
+
end
|
614
|
+
|
615
|
+
end
|
616
|
+
|
617
|
+
context "with enumerator" do
|
618
|
+
|
619
|
+
it "allows iteration" do
|
620
|
+
hash.each_key.each do |key|
|
621
|
+
keys << key
|
622
|
+
end
|
623
|
+
hash.close
|
624
|
+
|
625
|
+
keys.should == ['a', 'b']
|
626
|
+
end
|
627
|
+
|
628
|
+
it "allows iteration when empty" do
|
629
|
+
iterations = 0
|
630
|
+
|
631
|
+
hash = Hammerspace.new(path, options)
|
632
|
+
hash.each_key.each { |key,value| iterations += 1 }
|
633
|
+
hash.close
|
634
|
+
|
635
|
+
iterations.should == 0
|
636
|
+
end
|
637
|
+
|
638
|
+
it "allows updating during iteration" do
|
639
|
+
hash.each_key.each do |key|
|
640
|
+
keys << key
|
641
|
+
hash[key] = 'C'
|
642
|
+
end
|
643
|
+
|
644
|
+
keys.should == ['a', 'b']
|
645
|
+
|
646
|
+
hash['a'].should == 'C'
|
647
|
+
hash['b'].should == 'C'
|
648
|
+
|
649
|
+
hash.close
|
650
|
+
end
|
651
|
+
|
652
|
+
it "allows updating and reading during iteration" do
|
653
|
+
hash.each_key.each do |key|
|
654
|
+
keys << key
|
655
|
+
hash[key] = 'C'
|
656
|
+
hash[key].should == 'C'
|
657
|
+
end
|
658
|
+
|
659
|
+
keys.should == ['a', 'b']
|
660
|
+
|
661
|
+
hash['a'].should == 'C'
|
662
|
+
hash['b'].should == 'C'
|
663
|
+
|
664
|
+
hash.close
|
665
|
+
end
|
666
|
+
|
667
|
+
it "isolates iterators during iteration" do
|
668
|
+
hash.each_key.each do |key|
|
669
|
+
hash['b'] = 'C'
|
670
|
+
keys << key
|
671
|
+
end
|
672
|
+
|
673
|
+
keys.should == ['a', 'b']
|
674
|
+
|
675
|
+
hash['b'].should == 'C'
|
676
|
+
|
677
|
+
hash.close
|
678
|
+
end
|
679
|
+
|
680
|
+
end
|
681
|
+
|
682
|
+
end
|
683
|
+
|
684
|
+
describe "#each_value" do
|
685
|
+
|
686
|
+
let(:values) { [] }
|
687
|
+
let(:hash) do
|
688
|
+
h = Hammerspace.new(path, options)
|
689
|
+
h['a'] = 'A'
|
690
|
+
h['b'] = 'B'
|
691
|
+
h
|
692
|
+
end
|
693
|
+
|
694
|
+
context "with block" do
|
695
|
+
|
696
|
+
it "allows iteration" do
|
697
|
+
hash.each_value do |value|
|
698
|
+
values << value
|
699
|
+
end
|
700
|
+
hash.close
|
701
|
+
|
702
|
+
values.should == ['A', 'B']
|
703
|
+
end
|
704
|
+
|
705
|
+
it "allows iteration when empty" do
|
706
|
+
iterations = 0
|
707
|
+
|
708
|
+
hash = Hammerspace.new(path, options)
|
709
|
+
hash.each_value { |value| iterations += 1 }
|
710
|
+
hash.close
|
711
|
+
|
712
|
+
iterations.should == 0
|
713
|
+
end
|
714
|
+
|
715
|
+
it "allows updating during iteration" do
|
716
|
+
hash.each_value do |value|
|
717
|
+
values << value
|
718
|
+
hash['a'] = 'C'
|
719
|
+
end
|
720
|
+
|
721
|
+
values.should == ['A', 'B']
|
722
|
+
|
723
|
+
hash['a'].should == 'C'
|
724
|
+
|
725
|
+
hash.close
|
726
|
+
end
|
727
|
+
|
728
|
+
it "allows updating and reading during iteration" do
|
729
|
+
hash.each_value do |value|
|
730
|
+
values << value
|
731
|
+
hash['a'] = 'C'
|
732
|
+
hash['a'].should == 'C'
|
733
|
+
end
|
734
|
+
|
735
|
+
values.should == ['A', 'B']
|
736
|
+
|
737
|
+
hash['a'].should == 'C'
|
738
|
+
|
739
|
+
hash.close
|
740
|
+
end
|
741
|
+
|
742
|
+
it "isolates iterators during iteration" do
|
743
|
+
hash.each_value do |value|
|
744
|
+
hash['b'] = 'C'
|
745
|
+
values << value
|
746
|
+
end
|
747
|
+
|
748
|
+
values.should == ['A', 'B']
|
749
|
+
|
750
|
+
hash['b'].should == 'C'
|
751
|
+
|
752
|
+
hash.close
|
753
|
+
end
|
754
|
+
|
755
|
+
it "returns the hash" do
|
756
|
+
hash.each_value { |value| 'foo' }.should == hash
|
757
|
+
end
|
758
|
+
|
759
|
+
end
|
760
|
+
|
761
|
+
context "with enumerator" do
|
762
|
+
|
763
|
+
it "allows iteration" do
|
764
|
+
hash.each_value.each do |value|
|
765
|
+
values << value
|
766
|
+
end
|
767
|
+
hash.close
|
768
|
+
|
769
|
+
values.should == ['A', 'B']
|
770
|
+
end
|
771
|
+
|
772
|
+
it "allows iteration when empty" do
|
773
|
+
iterations = 0
|
774
|
+
|
775
|
+
hash = Hammerspace.new(path, options)
|
776
|
+
hash.each_value.each { |value| iterations += 1 }
|
777
|
+
hash.close
|
778
|
+
|
779
|
+
iterations.should == 0
|
780
|
+
end
|
781
|
+
|
782
|
+
it "allows updating during iteration" do
|
783
|
+
hash.each_value.each do |value|
|
784
|
+
values << value
|
785
|
+
hash['a'] = 'C'
|
786
|
+
end
|
787
|
+
|
788
|
+
values.should == ['A', 'B']
|
789
|
+
|
790
|
+
hash['a'].should == 'C'
|
791
|
+
|
792
|
+
hash.close
|
793
|
+
end
|
794
|
+
|
795
|
+
it "allows updating and reading during iteration" do
|
796
|
+
hash.each_value.each do |value|
|
797
|
+
values << value
|
798
|
+
hash['a'] = 'C'
|
799
|
+
hash['a'].should == 'C'
|
800
|
+
end
|
801
|
+
|
802
|
+
values.should == ['A', 'B']
|
803
|
+
|
804
|
+
hash['a'].should == 'C'
|
805
|
+
|
806
|
+
hash.close
|
807
|
+
end
|
808
|
+
|
809
|
+
it "isolates iterators during iteration" do
|
810
|
+
hash.each_value.each do |value|
|
811
|
+
hash['b'] = 'C'
|
812
|
+
values << value
|
813
|
+
end
|
814
|
+
|
815
|
+
values.should == ['A', 'B']
|
816
|
+
|
817
|
+
hash['b'].should == 'C'
|
818
|
+
|
819
|
+
hash.close
|
820
|
+
end
|
821
|
+
|
822
|
+
end
|
823
|
+
|
824
|
+
end
|
825
|
+
|
826
|
+
describe "#empty?" do
|
827
|
+
|
828
|
+
it "returns true when empty" do
|
829
|
+
hash = Hammerspace.new(path, options)
|
830
|
+
hash.empty?.should be_true
|
831
|
+
hash.close
|
832
|
+
end
|
833
|
+
|
834
|
+
it "returns false when not empty" do
|
835
|
+
hash = Hammerspace.new(path, options)
|
836
|
+
hash['foo'] = 'bar'
|
837
|
+
hash.empty?.should be_false
|
838
|
+
hash.close
|
839
|
+
end
|
840
|
+
|
841
|
+
end
|
842
|
+
|
843
|
+
describe "#eql?" do
|
844
|
+
|
845
|
+
it "returns false if different sizes" do
|
846
|
+
h1 = Hammerspace.new(File.join(path, '1'), options)
|
847
|
+
h1['a'] = 'A'
|
848
|
+
h1['b'] = 'B'
|
849
|
+
|
850
|
+
h2 = Hammerspace.new(File.join(path, '2'), options)
|
851
|
+
h2['a'] = 'A'
|
852
|
+
|
853
|
+
h1.should_not eql(h2)
|
854
|
+
|
855
|
+
h1.close
|
856
|
+
h2.close
|
857
|
+
end
|
858
|
+
|
859
|
+
it "does not consider default values" do
|
860
|
+
h1 = Hammerspace.new(File.join(path, '1'), options)
|
861
|
+
h1['a'] = 'A'
|
862
|
+
h1['b'] = 'B'
|
863
|
+
|
864
|
+
h2 = Hammerspace.new(File.join(path, '2'), options, 'B')
|
865
|
+
h2['a'] = 'A'
|
866
|
+
|
867
|
+
h1.should_not eql(h2)
|
868
|
+
|
869
|
+
h1.close
|
870
|
+
h2.close
|
871
|
+
end
|
872
|
+
|
873
|
+
it "returns false if different keys" do
|
874
|
+
h1 = Hammerspace.new(File.join(path, '1'), options)
|
875
|
+
h1['a'] = 'A'
|
876
|
+
h1['b'] = 'B'
|
877
|
+
|
878
|
+
h2 = Hammerspace.new(File.join(path, '2'), options)
|
879
|
+
h2['a'] = 'A'
|
880
|
+
h2['B'] = 'B'
|
881
|
+
|
882
|
+
h1.should_not eql(h2)
|
883
|
+
|
884
|
+
h1.close
|
885
|
+
h2.close
|
886
|
+
end
|
887
|
+
|
888
|
+
it "returns false if different values" do
|
889
|
+
h1 = Hammerspace.new(File.join(path, '1'), options)
|
890
|
+
h1['a'] = 'A'
|
891
|
+
h1['b'] = 'B'
|
892
|
+
|
893
|
+
h2 = Hammerspace.new(File.join(path, '2'), options)
|
894
|
+
h2['a'] = 'A'
|
895
|
+
h2['b'] = 'b'
|
896
|
+
|
897
|
+
h1.should_not eql(h2)
|
898
|
+
|
899
|
+
h1.close
|
900
|
+
h2.close
|
901
|
+
end
|
902
|
+
|
903
|
+
it "returns true if same keys and values" do
|
904
|
+
h1 = Hammerspace.new(File.join(path, '1'), options)
|
905
|
+
h1['a'] = 'A'
|
906
|
+
h1['b'] = 'B'
|
907
|
+
|
908
|
+
h2 = Hammerspace.new(File.join(path, '2'), options)
|
909
|
+
h2['a'] = 'A'
|
910
|
+
h2['b'] = 'B'
|
911
|
+
|
912
|
+
h1.should eql(h2)
|
913
|
+
|
914
|
+
h1.close
|
915
|
+
h2.close
|
916
|
+
end
|
917
|
+
|
918
|
+
it "works with hashes" do
|
919
|
+
hash = Hammerspace.new(File.join(path, '1'), options)
|
920
|
+
hash['a'] = 'A'
|
921
|
+
hash['b'] = 'B'
|
922
|
+
hash.should eql({'a' => 'A', 'b' => 'B'})
|
923
|
+
hash.close
|
924
|
+
end
|
925
|
+
|
926
|
+
end
|
927
|
+
|
928
|
+
describe "#fetch" do
|
929
|
+
|
930
|
+
it "returns value if key exists" do
|
931
|
+
hash = Hammerspace.new(path, options)
|
932
|
+
hash['foo'] = 'bar'
|
933
|
+
hash.fetch('foo').should == 'bar'
|
934
|
+
end
|
935
|
+
|
936
|
+
it "calls block to determine value if key does not exist" do
|
937
|
+
hash = Hammerspace.new(path, options)
|
938
|
+
hash.fetch('foo') { |key| "block#{key}" }.should == "blockfoo"
|
939
|
+
hash.close
|
940
|
+
end
|
941
|
+
|
942
|
+
it "returns default value if key does not exist" do
|
943
|
+
hash = Hammerspace.new(path, options)
|
944
|
+
hash.fetch('foo', 'default').should == 'default'
|
945
|
+
hash.close
|
946
|
+
end
|
947
|
+
|
948
|
+
it "calls block to determine value if key does not exist and both second argument and block are passed" do
|
949
|
+
hash = Hammerspace.new(path, options)
|
950
|
+
hash.fetch('foo', 'default') { |key| "block#{key}" }.should == "blockfoo"
|
951
|
+
hash.close
|
952
|
+
end
|
953
|
+
|
954
|
+
it "raises KeyError if key does not exist" do
|
955
|
+
hash = Hammerspace.new(path, options)
|
956
|
+
expect {
|
957
|
+
hash.fetch('foo')
|
958
|
+
}.to raise_error(KeyError)
|
959
|
+
hash.close
|
960
|
+
end
|
961
|
+
|
962
|
+
it "raises ArgumentError if a third argument is passed" do
|
963
|
+
hash = Hammerspace.new(path, options)
|
964
|
+
expect {
|
965
|
+
hash.fetch('foo', 'default', 'bogus')
|
966
|
+
}.to raise_error(ArgumentError)
|
967
|
+
hash.close
|
968
|
+
end
|
969
|
+
|
970
|
+
end
|
971
|
+
|
972
|
+
describe "#flatten" do
|
973
|
+
|
974
|
+
it "returns an array of key value pairs" do
|
975
|
+
hash = Hammerspace.new(path, options)
|
976
|
+
hash['a'] = 'A'
|
977
|
+
hash['b'] = 'B'
|
978
|
+
hash.flatten.should == ['a', 'A', 'b', 'B']
|
979
|
+
hash.close
|
980
|
+
end
|
981
|
+
|
982
|
+
it "returns an empty array when empty" do
|
983
|
+
hash = Hammerspace.new(path, options)
|
984
|
+
hash.flatten.should == []
|
985
|
+
hash.close
|
986
|
+
end
|
987
|
+
|
988
|
+
it "accepts an optional level argument" do
|
989
|
+
hash = Hammerspace.new(path, options)
|
990
|
+
hash['a'] = 'A'
|
991
|
+
hash['b'] = 'B'
|
992
|
+
hash.flatten(2).should == ['a', 'A', 'b', 'B']
|
993
|
+
hash.close
|
994
|
+
end
|
995
|
+
|
996
|
+
it "raises ArgumentError if a second argument is passed" do
|
997
|
+
hash = Hammerspace.new(path, options)
|
998
|
+
expect {
|
999
|
+
hash.flatten(1, 'bogus')
|
1000
|
+
}.to raise_error(ArgumentError)
|
1001
|
+
hash.close
|
1002
|
+
end
|
1003
|
+
|
1004
|
+
end
|
1005
|
+
|
1006
|
+
describe "#has_key?" do
|
1007
|
+
|
1008
|
+
it "returns true when key is present" do
|
1009
|
+
hash = Hammerspace.new(path, options)
|
1010
|
+
hash['foo'] = 'bar'
|
1011
|
+
hash.has_key?('foo').should be_true
|
1012
|
+
hash.close
|
1013
|
+
end
|
1014
|
+
|
1015
|
+
it "returns false when key is not present" do
|
1016
|
+
hash = Hammerspace.new(path, options)
|
1017
|
+
hash['foo'] = 'bar'
|
1018
|
+
hash.has_key?('otherkey').should be_false
|
1019
|
+
hash.close
|
1020
|
+
end
|
1021
|
+
|
1022
|
+
it "returns false when empty" do
|
1023
|
+
hash = Hammerspace.new(path, options)
|
1024
|
+
hash.has_key?('foo').should be_false
|
1025
|
+
hash.close
|
1026
|
+
end
|
1027
|
+
|
1028
|
+
end
|
1029
|
+
|
1030
|
+
describe "#has_value?" do
|
1031
|
+
|
1032
|
+
it "returns true when value is present" do
|
1033
|
+
hash = Hammerspace.new(path, options)
|
1034
|
+
hash['foo'] = 'bar'
|
1035
|
+
hash.has_value?('bar').should be_true
|
1036
|
+
hash.close
|
1037
|
+
end
|
1038
|
+
|
1039
|
+
it "returns false when value is not present" do
|
1040
|
+
hash = Hammerspace.new(path, options)
|
1041
|
+
hash['foo'] = 'bar'
|
1042
|
+
hash.has_value?('othervalue').should be_false
|
1043
|
+
hash.close
|
1044
|
+
end
|
1045
|
+
|
1046
|
+
it "returns false when empty" do
|
1047
|
+
hash = Hammerspace.new(path, options)
|
1048
|
+
hash.has_value?('foo').should be_false
|
1049
|
+
hash.close
|
1050
|
+
end
|
1051
|
+
|
1052
|
+
end
|
1053
|
+
|
1054
|
+
describe "#keep_if" do
|
1055
|
+
|
1056
|
+
let(:hash) do
|
1057
|
+
h = Hammerspace.new(path, options)
|
1058
|
+
h['a'] = 'A'
|
1059
|
+
h['b'] = 'B'
|
1060
|
+
h
|
1061
|
+
end
|
1062
|
+
|
1063
|
+
context "with block" do
|
1064
|
+
|
1065
|
+
it "keeps when true" do
|
1066
|
+
hash.keep_if { |key,value| key == 'b' }
|
1067
|
+
hash.has_key?('a').should be_false
|
1068
|
+
hash['a'].should be_nil
|
1069
|
+
hash.has_key?('b').should be_true
|
1070
|
+
hash['b'].should == 'B'
|
1071
|
+
hash.close
|
1072
|
+
end
|
1073
|
+
|
1074
|
+
it "returns the hash" do
|
1075
|
+
hash.keep_if { |key,value| true }.should == hash
|
1076
|
+
hash.close
|
1077
|
+
end
|
1078
|
+
|
1079
|
+
end
|
1080
|
+
|
1081
|
+
context "with enumerator" do
|
1082
|
+
|
1083
|
+
it "keeps when true" do
|
1084
|
+
hash.keep_if.each { |key,value| key == 'b' }
|
1085
|
+
hash.has_key?('a').should be_false
|
1086
|
+
hash['a'].should be_nil
|
1087
|
+
hash.has_key?('b').should be_true
|
1088
|
+
hash['b'].should == 'B'
|
1089
|
+
hash.close
|
1090
|
+
end
|
1091
|
+
|
1092
|
+
end
|
1093
|
+
|
1094
|
+
end
|
1095
|
+
|
1096
|
+
describe "#key" do
|
1097
|
+
|
1098
|
+
it "returns value if key exists" do
|
1099
|
+
hash = Hammerspace.new(path, options)
|
1100
|
+
hash['foo'] = 'bar'
|
1101
|
+
hash.key('foo').should == 'bar'
|
1102
|
+
end
|
1103
|
+
|
1104
|
+
it "returns nil if key does not exist" do
|
1105
|
+
hash = Hammerspace.new(path, options)
|
1106
|
+
hash.key('foo').should be_nil
|
1107
|
+
end
|
1108
|
+
|
1109
|
+
end
|
1110
|
+
|
1111
|
+
describe "#keys" do
|
1112
|
+
|
1113
|
+
it "returns keys" do
|
1114
|
+
hash = Hammerspace.new(path, options)
|
1115
|
+
hash['a'] = 'A'
|
1116
|
+
hash['b'] = 'B'
|
1117
|
+
hash.keys.should == ['a', 'b']
|
1118
|
+
hash.close
|
1119
|
+
end
|
1120
|
+
|
1121
|
+
it "returns empty array when empty" do
|
1122
|
+
hash = Hammerspace.new(path, options)
|
1123
|
+
hash.keys.should == []
|
1124
|
+
hash.close
|
1125
|
+
end
|
1126
|
+
|
1127
|
+
end
|
1128
|
+
|
1129
|
+
describe "#merge!" do
|
1130
|
+
|
1131
|
+
it "adds new values" do
|
1132
|
+
hash = Hammerspace.new(path, options)
|
1133
|
+
hash.merge!({'foo' => 'bar'})
|
1134
|
+
hash['foo'].should == 'bar'
|
1135
|
+
hash.close
|
1136
|
+
end
|
1137
|
+
|
1138
|
+
it "updates existing values" do
|
1139
|
+
hash = Hammerspace.new(path, options)
|
1140
|
+
hash['foo'] = 'bar'
|
1141
|
+
hash.merge!({'foo' => 'newvalue'})
|
1142
|
+
hash['foo'].should == 'newvalue'
|
1143
|
+
hash.close
|
1144
|
+
end
|
1145
|
+
|
1146
|
+
it "calls block to determine value on duplicates" do
|
1147
|
+
hash = Hammerspace.new(path, options)
|
1148
|
+
hash['foo'] = 'bar'
|
1149
|
+
hash.merge!({'foo' => 'newvalue'}) { |key, v1, v2| v1 + v2 }
|
1150
|
+
hash['foo'].should == 'barnewvalue'
|
1151
|
+
hash.close
|
1152
|
+
end
|
1153
|
+
|
1154
|
+
it "returns the hash" do
|
1155
|
+
hash = Hammerspace.new(path, options)
|
1156
|
+
hash.merge!({}).should == hash
|
1157
|
+
hash.close
|
1158
|
+
end
|
1159
|
+
|
1160
|
+
end
|
1161
|
+
|
1162
|
+
describe "#rassoc" do
|
1163
|
+
|
1164
|
+
it "returns key value pair when value is present" do
|
1165
|
+
hash = Hammerspace.new(path, options)
|
1166
|
+
hash['foo'] = 'bar'
|
1167
|
+
hash.rassoc('bar').should == ['foo', 'bar']
|
1168
|
+
hash.close
|
1169
|
+
end
|
1170
|
+
|
1171
|
+
it "returns first key value pair when value is present multiple times" do
|
1172
|
+
hash = Hammerspace.new(path, options)
|
1173
|
+
hash['foo'] = 'bar'
|
1174
|
+
hash['otherkey'] = 'bar'
|
1175
|
+
hash.rassoc('bar').should == ['foo', 'bar']
|
1176
|
+
hash.close
|
1177
|
+
end
|
1178
|
+
|
1179
|
+
it "returns nil when value is not present" do
|
1180
|
+
hash = Hammerspace.new(path, options)
|
1181
|
+
hash['foo'] = 'bar'
|
1182
|
+
hash.rassoc('otherkey').should be_nil
|
1183
|
+
hash.close
|
1184
|
+
end
|
1185
|
+
|
1186
|
+
it "returns nil when empty" do
|
1187
|
+
hash = Hammerspace.new(path, options)
|
1188
|
+
hash.rassoc('foo').should be_nil
|
1189
|
+
hash.close
|
1190
|
+
end
|
1191
|
+
|
1192
|
+
end
|
1193
|
+
|
1194
|
+
describe "#reject!" do
|
1195
|
+
|
1196
|
+
let(:hash) do
|
1197
|
+
h = Hammerspace.new(path, options)
|
1198
|
+
h['a'] = 'A'
|
1199
|
+
h['b'] = 'B'
|
1200
|
+
h
|
1201
|
+
end
|
1202
|
+
|
1203
|
+
context "with block" do
|
1204
|
+
|
1205
|
+
it "deletes when true" do
|
1206
|
+
hash.reject! { |key,value| key == 'a' }
|
1207
|
+
hash.has_key?('a').should be_false
|
1208
|
+
hash['a'].should be_nil
|
1209
|
+
hash.has_key?('b').should be_true
|
1210
|
+
hash['b'].should == 'B'
|
1211
|
+
hash.close
|
1212
|
+
end
|
1213
|
+
|
1214
|
+
it "returns the hash if items deleted" do
|
1215
|
+
hash.reject! { |key,value| true }.should == hash
|
1216
|
+
hash.close
|
1217
|
+
end
|
1218
|
+
|
1219
|
+
it "returns nil if no items deleted" do
|
1220
|
+
hash.reject! { |key,value| false }.should be_nil
|
1221
|
+
hash.close
|
1222
|
+
end
|
1223
|
+
|
1224
|
+
end
|
1225
|
+
|
1226
|
+
context "with enumerator" do
|
1227
|
+
|
1228
|
+
it "deletes when true" do
|
1229
|
+
hash.reject!.each { |key,value| key == 'a' }
|
1230
|
+
hash.has_key?('a').should be_false
|
1231
|
+
hash['a'].should be_nil
|
1232
|
+
hash.has_key?('b').should be_true
|
1233
|
+
hash['b'].should == 'B'
|
1234
|
+
hash.close
|
1235
|
+
end
|
1236
|
+
|
1237
|
+
end
|
1238
|
+
|
1239
|
+
end
|
1240
|
+
|
1241
|
+
describe "#replace" do
|
1242
|
+
|
1243
|
+
it "removes values" do
|
1244
|
+
hash = Hammerspace.new(path, options)
|
1245
|
+
hash['a'] = 'A'
|
1246
|
+
hash.close
|
1247
|
+
|
1248
|
+
hash.replace({'b' => 'B'})
|
1249
|
+
hash['a'].should be_nil
|
1250
|
+
hash['b'].should == 'B'
|
1251
|
+
hash.close
|
1252
|
+
end
|
1253
|
+
|
1254
|
+
it "updates existing values" do
|
1255
|
+
hash = Hammerspace.new(path, options)
|
1256
|
+
hash['foo'] = 'bar'
|
1257
|
+
hash.close
|
1258
|
+
|
1259
|
+
hash.replace({'foo' => 'newvalue'})
|
1260
|
+
hash['foo'].should == 'newvalue'
|
1261
|
+
hash.close
|
1262
|
+
end
|
1263
|
+
|
1264
|
+
it "returns the hash" do
|
1265
|
+
hash = Hammerspace.new(path, options)
|
1266
|
+
hash.replace({}).should == hash
|
1267
|
+
hash.close
|
1268
|
+
end
|
1269
|
+
|
1270
|
+
end
|
1271
|
+
|
1272
|
+
describe "#select!" do
|
1273
|
+
|
1274
|
+
let(:hash) do
|
1275
|
+
h = Hammerspace.new(path, options)
|
1276
|
+
h['a'] = 'A'
|
1277
|
+
h['b'] = 'B'
|
1278
|
+
h
|
1279
|
+
end
|
1280
|
+
|
1281
|
+
context "with block" do
|
1282
|
+
|
1283
|
+
it "keeps when true" do
|
1284
|
+
hash.select! { |key,value| key == 'b' }
|
1285
|
+
hash.has_key?('a').should be_false
|
1286
|
+
hash['a'].should be_nil
|
1287
|
+
hash.has_key?('b').should be_true
|
1288
|
+
hash['b'].should == 'B'
|
1289
|
+
hash.close
|
1290
|
+
end
|
1291
|
+
|
1292
|
+
it "returns the hash if items deleted" do
|
1293
|
+
hash.select! { |key,value| false }.should == hash
|
1294
|
+
hash.close
|
1295
|
+
end
|
1296
|
+
|
1297
|
+
it "returns nil if no items deleted" do
|
1298
|
+
hash.select! { |key,value| true }.should be_nil
|
1299
|
+
hash.close
|
1300
|
+
end
|
1301
|
+
|
1302
|
+
end
|
1303
|
+
|
1304
|
+
context "with enumerator" do
|
1305
|
+
|
1306
|
+
it "keeps when true" do
|
1307
|
+
hash.select!.each { |key,value| key == 'b' }
|
1308
|
+
hash.has_key?('a').should be_false
|
1309
|
+
hash['a'].should be_nil
|
1310
|
+
hash.has_key?('b').should be_true
|
1311
|
+
hash['b'].should == 'B'
|
1312
|
+
hash.close
|
1313
|
+
end
|
1314
|
+
|
1315
|
+
end
|
1316
|
+
|
1317
|
+
end
|
1318
|
+
|
1319
|
+
describe "#shift" do
|
1320
|
+
|
1321
|
+
it "removes and returns the first key value pair" do
|
1322
|
+
hash = Hammerspace.new(path, options)
|
1323
|
+
hash['a'] = 'A'
|
1324
|
+
hash['b'] = 'B'
|
1325
|
+
hash.shift.should == ['a', 'A']
|
1326
|
+
hash.keys.should == ['b']
|
1327
|
+
hash.values.should == ['B']
|
1328
|
+
hash.close
|
1329
|
+
end
|
1330
|
+
|
1331
|
+
it "returns the default value if empty" do
|
1332
|
+
hash = Hammerspace.new(path, options, 'default')
|
1333
|
+
hash.shift.should == 'default'
|
1334
|
+
hash.close
|
1335
|
+
end
|
1336
|
+
|
1337
|
+
end
|
1338
|
+
|
1339
|
+
describe "#size" do
|
1340
|
+
|
1341
|
+
it "returns size" do
|
1342
|
+
hash = Hammerspace.new(path, options)
|
1343
|
+
hash['a'] = 'A'
|
1344
|
+
hash['b'] = 'B'
|
1345
|
+
hash.size.should == 2
|
1346
|
+
hash.close
|
1347
|
+
end
|
1348
|
+
|
1349
|
+
it "returns 0 when empty" do
|
1350
|
+
hash = Hammerspace.new(path, options)
|
1351
|
+
hash.size.should == 0
|
1352
|
+
hash.close
|
1353
|
+
end
|
1354
|
+
|
1355
|
+
end
|
1356
|
+
|
1357
|
+
describe "#to_a" do
|
1358
|
+
|
1359
|
+
it "returns an array of key value pairs" do
|
1360
|
+
hash = Hammerspace.new(path, options)
|
1361
|
+
hash['a'] = 'A'
|
1362
|
+
hash['b'] = 'B'
|
1363
|
+
hash.to_a.should == [['a', 'A'], ['b', 'B']]
|
1364
|
+
hash.close
|
1365
|
+
end
|
1366
|
+
|
1367
|
+
it "returns an empty array when empty" do
|
1368
|
+
hash = Hammerspace.new(path, options)
|
1369
|
+
hash.to_a.should == []
|
1370
|
+
hash.close
|
1371
|
+
end
|
1372
|
+
|
1373
|
+
end
|
1374
|
+
|
1375
|
+
describe "#to_hash" do
|
1376
|
+
|
1377
|
+
it "returns a hash" do
|
1378
|
+
hash = Hammerspace.new(path, options)
|
1379
|
+
hash['a'] = 'A'
|
1380
|
+
hash['b'] = 'B'
|
1381
|
+
hash.to_hash.should == {'a' => 'A', 'b' => 'B'}
|
1382
|
+
hash.close
|
1383
|
+
end
|
1384
|
+
|
1385
|
+
it "returns an empty hash when empty" do
|
1386
|
+
hash = Hammerspace.new(path, options)
|
1387
|
+
hash.to_hash.should == {}
|
1388
|
+
hash.close
|
1389
|
+
end
|
1390
|
+
|
1391
|
+
end
|
1392
|
+
|
1393
|
+
describe "#uid" do
|
1394
|
+
|
1395
|
+
it "returns uid" do
|
1396
|
+
hash = Hammerspace.new(path, options)
|
1397
|
+
hash['foo'] = 'bar'
|
1398
|
+
hash.close
|
1399
|
+
|
1400
|
+
hash = Hammerspace.new(path, options)
|
1401
|
+
hash.uid.should_not be_nil
|
1402
|
+
hash.close
|
1403
|
+
end
|
1404
|
+
|
1405
|
+
it "returns nil when empty" do
|
1406
|
+
hash = Hammerspace.new(path, options)
|
1407
|
+
hash.uid.should be_nil
|
1408
|
+
hash.close
|
1409
|
+
end
|
1410
|
+
|
1411
|
+
it "returns same uid throughout isolated read" do
|
1412
|
+
writer = Hammerspace.new(path, options)
|
1413
|
+
writer['foo'] = 'bar'
|
1414
|
+
writer.close
|
1415
|
+
|
1416
|
+
reader = Hammerspace.new(path, options)
|
1417
|
+
uid = reader.uid
|
1418
|
+
|
1419
|
+
writer = Hammerspace.new(path, options)
|
1420
|
+
writer['foo'] = 'newvalue'
|
1421
|
+
writer.close
|
1422
|
+
|
1423
|
+
reader.uid.should == uid
|
1424
|
+
reader.close
|
1425
|
+
end
|
1426
|
+
|
1427
|
+
it "returns different uid after close/reopen" do
|
1428
|
+
writer = Hammerspace.new(path, options)
|
1429
|
+
writer['foo'] = 'bar'
|
1430
|
+
writer.close
|
1431
|
+
|
1432
|
+
reader = Hammerspace.new(path, options)
|
1433
|
+
uid = reader.uid
|
1434
|
+
|
1435
|
+
writer = Hammerspace.new(path, options)
|
1436
|
+
writer['foo'] = 'newvalue'
|
1437
|
+
writer.close
|
1438
|
+
|
1439
|
+
reader.uid.should == uid
|
1440
|
+
reader.close
|
1441
|
+
reader.uid.should_not == uid
|
1442
|
+
reader.close
|
1443
|
+
end
|
1444
|
+
|
1445
|
+
end
|
1446
|
+
|
1447
|
+
describe "#values" do
|
1448
|
+
|
1449
|
+
it "returns values" do
|
1450
|
+
hash = Hammerspace.new(path, options)
|
1451
|
+
hash['a'] = 'A'
|
1452
|
+
hash['b'] = 'B'
|
1453
|
+
hash.values.should == ['A', 'B']
|
1454
|
+
hash.close
|
1455
|
+
end
|
1456
|
+
|
1457
|
+
it "returns empty array when empty" do
|
1458
|
+
hash = Hammerspace.new(path, options)
|
1459
|
+
hash.values.should == []
|
1460
|
+
hash.close
|
1461
|
+
end
|
1462
|
+
|
1463
|
+
end
|
1464
|
+
|
1465
|
+
describe "#values_at" do
|
1466
|
+
|
1467
|
+
it "returns values" do
|
1468
|
+
hash = Hammerspace.new(path, options)
|
1469
|
+
hash['a'] = 'A'
|
1470
|
+
hash['b'] = 'B'
|
1471
|
+
hash.values_at('b', 'a').should == ['B', 'A']
|
1472
|
+
hash.close
|
1473
|
+
end
|
1474
|
+
|
1475
|
+
it "returns default values when keys do not exist" do
|
1476
|
+
hash = Hammerspace.new(path, options)
|
1477
|
+
hash.default = 'default'
|
1478
|
+
hash['a'] = 'A'
|
1479
|
+
hash.values_at('a', 'b').should == ['A', 'default']
|
1480
|
+
hash.close
|
1481
|
+
end
|
1482
|
+
|
1483
|
+
end
|
1484
|
+
|
1485
|
+
end
|
1486
|
+
end
|
1487
|
+
end
|