remcached 0.3.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.
- data/.gitignore +2 -0
- data/README.rst +105 -0
- data/Rakefile +13 -0
- data/VERSION.yml +4 -0
- data/examples/fill.rb +70 -0
- data/lib/remcached/client.rb +143 -0
- data/lib/remcached/const.rb +64 -0
- data/lib/remcached/pack_array.rb +46 -0
- data/lib/remcached/packet.rb +275 -0
- data/lib/remcached.rb +158 -0
- data/remcached.gemspec +55 -0
- data/spec/client_spec.rb +36 -0
- data/spec/memcached_spec.rb +268 -0
- data/spec/packet_spec.rb +125 -0
- metadata +71 -0
@@ -0,0 +1,268 @@
|
|
1
|
+
$: << File.dirname(__FILE__) + '/../lib'
|
2
|
+
require 'remcached'
|
3
|
+
|
4
|
+
describe Memcached do
|
5
|
+
def run(&block)
|
6
|
+
EM.run do
|
7
|
+
Memcached.servers = %w(127.0.0.2 localhost:11212 localhost localhost)
|
8
|
+
|
9
|
+
@timer = EM::PeriodicTimer.new(0.01) do
|
10
|
+
# at least localhost & localhost
|
11
|
+
if Memcached.usable_clients.length >= 2
|
12
|
+
@timer.cancel
|
13
|
+
block.call
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
def stop
|
20
|
+
Memcached.servers = []
|
21
|
+
EM.stop
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
context "when doing a simple operation" do
|
26
|
+
it "should add a value" do
|
27
|
+
run do
|
28
|
+
Memcached.add(:key => 'Hello',
|
29
|
+
:value => 'World') do |result|
|
30
|
+
result.should be_kind_of(Memcached::Response)
|
31
|
+
result[:status].should == Memcached::Errors::NO_ERROR
|
32
|
+
result[:cas].should_not == 0
|
33
|
+
stop
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should get a value" do
|
39
|
+
run do
|
40
|
+
Memcached.get(:key => 'Hello') do |result|
|
41
|
+
result.should be_kind_of(Memcached::Response)
|
42
|
+
result[:status].should == Memcached::Errors::NO_ERROR
|
43
|
+
result[:value].should == 'World'
|
44
|
+
result[:cas].should_not == 0
|
45
|
+
@old_cas = result[:cas]
|
46
|
+
stop
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should set a value" do
|
52
|
+
run do
|
53
|
+
Memcached.set(:key => 'Hello',
|
54
|
+
:value => 'Planet') do |result|
|
55
|
+
result.should be_kind_of(Memcached::Response)
|
56
|
+
result[:status].should == Memcached::Errors::NO_ERROR
|
57
|
+
result[:cas].should_not == 0
|
58
|
+
result[:cas].should_not == @old_cas
|
59
|
+
stop
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should get a value" do
|
65
|
+
run do
|
66
|
+
Memcached.get(:key => 'Hello') do |result|
|
67
|
+
result.should be_kind_of(Memcached::Response)
|
68
|
+
result[:status].should == Memcached::Errors::NO_ERROR
|
69
|
+
result[:value].should == 'Planet'
|
70
|
+
result[:cas].should_not == @old_cas
|
71
|
+
stop
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should delete a value" do
|
77
|
+
run do
|
78
|
+
Memcached.delete(:key => 'Hello') do |result|
|
79
|
+
result.should be_kind_of(Memcached::Response)
|
80
|
+
result[:status].should == Memcached::Errors::NO_ERROR
|
81
|
+
stop
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should not get a value" do
|
87
|
+
run do
|
88
|
+
Memcached.get(:key => 'Hello') do |result|
|
89
|
+
result.should be_kind_of(Memcached::Response)
|
90
|
+
result[:status].should == Memcached::Errors::KEY_NOT_FOUND
|
91
|
+
stop
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
$n = 100
|
97
|
+
context "when incrementing a counter #{$n} times" do
|
98
|
+
it "should initialize the counter" do
|
99
|
+
run do
|
100
|
+
Memcached.set(:key => 'counter',
|
101
|
+
:value => '0') do |result|
|
102
|
+
stop
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should count #{$n} times" do
|
108
|
+
@counted = 0
|
109
|
+
def count
|
110
|
+
Memcached.get(:key => 'counter') do |result|
|
111
|
+
result[:status].should == Memcached::Errors::NO_ERROR
|
112
|
+
value = result[:value].to_i
|
113
|
+
Memcached.set(:key => 'counter',
|
114
|
+
:value => (value + 1).to_s,
|
115
|
+
:cas => result[:cas]) do |result|
|
116
|
+
if result[:status] == Memcached::Errors::KEY_EXISTS
|
117
|
+
count # again
|
118
|
+
else
|
119
|
+
result[:status].should == Memcached::Errors::NO_ERROR
|
120
|
+
@counted += 1
|
121
|
+
stop if @counted >= $n
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
run do
|
127
|
+
$n.times { count }
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
it "should have counted up to #{$n}" do
|
132
|
+
run do
|
133
|
+
Memcached.get(:key => 'counter') do |result|
|
134
|
+
result[:status].should == Memcached::Errors::NO_ERROR
|
135
|
+
result[:value].to_i.should == $n
|
136
|
+
stop
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
context "when using multiple servers" do
|
144
|
+
it "should not return the same hash for the succeeding key" do
|
145
|
+
run do
|
146
|
+
Memcached.hash_key('0').should_not == Memcached.hash_key('1')
|
147
|
+
stop
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should not return the same client for the succeeding key" do
|
152
|
+
run do
|
153
|
+
# wait for 2nd client to be connected
|
154
|
+
EM::Timer.new(0.1) do
|
155
|
+
Memcached.client_for_key('0').should_not == Memcached.client_for_key('1')
|
156
|
+
stop
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
it "should spread load (observe from outside :-)" do
|
162
|
+
run do
|
163
|
+
|
164
|
+
n = 10000
|
165
|
+
replies = 0
|
166
|
+
n.times do |i|
|
167
|
+
Memcached.set(:key => "#{i % 100}",
|
168
|
+
:value => rand(1 << 31).to_s) {
|
169
|
+
replies += 1
|
170
|
+
stop if replies >= n
|
171
|
+
}
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
context "when manipulating multiple records at once" do
|
179
|
+
before :all do
|
180
|
+
@n = 10
|
181
|
+
end
|
182
|
+
|
183
|
+
def key(n)
|
184
|
+
"test:item:#{n}"
|
185
|
+
end
|
186
|
+
|
187
|
+
it "should add some items" do
|
188
|
+
run do
|
189
|
+
items = []
|
190
|
+
@n.times { |i|
|
191
|
+
items << { :key => key(i),
|
192
|
+
:value => 'Foo',
|
193
|
+
:expiration => 20 } if i % 2 == 0
|
194
|
+
}
|
195
|
+
Memcached.multi_add(items) { |responses|
|
196
|
+
stop
|
197
|
+
@n.times { |i|
|
198
|
+
if i % 2 == 0 && (response_i = responses[key(i)])
|
199
|
+
response_i[:status].should == Memcached::Errors::NO_ERROR
|
200
|
+
end
|
201
|
+
}
|
202
|
+
}
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
it "should get all items" do
|
207
|
+
run do
|
208
|
+
items = []
|
209
|
+
@n.times { |i|
|
210
|
+
items << { :key => key(i) }
|
211
|
+
}
|
212
|
+
Memcached.multi_get(items) { |responses|
|
213
|
+
stop
|
214
|
+
@n.times { |i|
|
215
|
+
if i % 2 == 0
|
216
|
+
responses.should have_key(key(i))
|
217
|
+
responses[key(i)][:status].should == Memcached::Errors::NO_ERROR
|
218
|
+
responses[key(i)][:value].should == 'Foo'
|
219
|
+
else
|
220
|
+
# either no response because request was quiet, or not
|
221
|
+
# found in case of last response
|
222
|
+
if (response_i = responses[key(i)])
|
223
|
+
response_i[:status].should == Memcached::Errors::KEY_NOT_FOUND
|
224
|
+
end
|
225
|
+
end
|
226
|
+
}
|
227
|
+
}
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
it "should delete all items" do
|
232
|
+
run do
|
233
|
+
items = []
|
234
|
+
@n.times { |i|
|
235
|
+
items << { :key => key(i) }
|
236
|
+
}
|
237
|
+
Memcached.multi_delete(items) { |responses|
|
238
|
+
stop
|
239
|
+
@n.times { |i|
|
240
|
+
if i % 2 == 0
|
241
|
+
# either no response because request was quiet, or ok in
|
242
|
+
# case of last response
|
243
|
+
if (response_i = responses[key(i)])
|
244
|
+
response_i[:status].should == Memcached::Errors::NO_ERROR
|
245
|
+
end
|
246
|
+
else
|
247
|
+
responses[key(i)][:status].should == Memcached::Errors::KEY_NOT_FOUND
|
248
|
+
end
|
249
|
+
}
|
250
|
+
}
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
context "when the multi operation is empty" do
|
255
|
+
it "should return immediately" do
|
256
|
+
@results = []
|
257
|
+
@calls = 0
|
258
|
+
Memcached.multi_add([]) { |responses|
|
259
|
+
@results += responses
|
260
|
+
@calls += 1
|
261
|
+
}
|
262
|
+
@results.should be_empty
|
263
|
+
@calls.should == 1
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
end
|
data/spec/packet_spec.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
$: << File.dirname(__FILE__) + '/../lib'
|
2
|
+
require 'remcached'
|
3
|
+
|
4
|
+
describe Memcached::Packet do
|
5
|
+
|
6
|
+
context "when generating a request" do
|
7
|
+
it "should set default values" do
|
8
|
+
pkt = Memcached::Request.new
|
9
|
+
pkt[:magic].should == 0x80
|
10
|
+
end
|
11
|
+
|
12
|
+
context "example 4.2.1" do
|
13
|
+
before :all do
|
14
|
+
pkt = Memcached::Request.new(:key => 'Hello')
|
15
|
+
@s = pkt.to_s
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should serialize correctly" do
|
19
|
+
@s.should == "\x80\x00\x00\x05" +
|
20
|
+
"\x00\x00\x00\x00" +
|
21
|
+
"\x00\x00\x00\x05" +
|
22
|
+
"\x00\x00\x00\x00" +
|
23
|
+
"\x00\x00\x00\x00" +
|
24
|
+
"\x00\x00\x00\x00" +
|
25
|
+
"Hello"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "example 4.3.1 (add)" do
|
30
|
+
before :all do
|
31
|
+
pkt = Memcached::Request::Add.new(:flags => 0xdeadbeef,
|
32
|
+
:expiration => 0xe10,
|
33
|
+
:key => "Hello",
|
34
|
+
:value => "World")
|
35
|
+
@s = pkt.to_s
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should serialize correctly" do
|
39
|
+
@s.should == "\x80\x02\x00\x05" +
|
40
|
+
"\x08\x00\x00\x00" +
|
41
|
+
"\x00\x00\x00\x12" +
|
42
|
+
"\x00\x00\x00\x00" +
|
43
|
+
"\x00\x00\x00\x00" +
|
44
|
+
"\x00\x00\x00\x00" +
|
45
|
+
"\xde\xad\xbe\xef" +
|
46
|
+
"\x00\x00\x0e\x10" +
|
47
|
+
"Hello" +
|
48
|
+
"World"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "when parsing a response" do
|
54
|
+
context "example 4.1.1" do
|
55
|
+
before :all do
|
56
|
+
s = "\x81\x00\x00\x00\x00\x00\x00\x01" +
|
57
|
+
"\x00\x00\x00\x09\x00\x00\x00\x00" +
|
58
|
+
"\x00\x00\x00\x00\x00\x00\x00\x00" +
|
59
|
+
"Not found"
|
60
|
+
@pkt = Memcached::Response.parse_header(s[0..23])
|
61
|
+
@pkt.parse_body(s[24..-1])
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should return the right class according to magic & opcode" do
|
65
|
+
@pkt[:magic].should == 0x81
|
66
|
+
@pkt[:opcode].should == 0
|
67
|
+
@pkt.class.should == Memcached::Response
|
68
|
+
end
|
69
|
+
it "should return the right data type" do
|
70
|
+
@pkt[:data_type].should == 0
|
71
|
+
end
|
72
|
+
it "should return the right status" do
|
73
|
+
@pkt[:status].should == Memcached::Errors::KEY_NOT_FOUND
|
74
|
+
end
|
75
|
+
it "should return the right opaque" do
|
76
|
+
@pkt[:opaque].should == 0
|
77
|
+
end
|
78
|
+
it "should return the right CAS" do
|
79
|
+
@pkt[:cas].should == 0
|
80
|
+
end
|
81
|
+
it "should parse the body correctly" do
|
82
|
+
@pkt[:extras].should be_empty
|
83
|
+
@pkt[:key].should == ""
|
84
|
+
@pkt[:value].should == "Not found"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context "example 4.2.1" do
|
89
|
+
before :all do
|
90
|
+
s = "\x81\x00\x00\x00" +
|
91
|
+
"\x04\x00\x00\x00" +
|
92
|
+
"\x00\x00\x00\x09" +
|
93
|
+
"\x00\x00\x00\x00" +
|
94
|
+
"\x00\x00\x00\x00" +
|
95
|
+
"\x00\x00\x00\x01" +
|
96
|
+
"\xde\xad\xbe\xef" +
|
97
|
+
"World"
|
98
|
+
@pkt = Memcached::Response.parse_header(s[0..23])
|
99
|
+
@pkt.parse_body(s[24..-1])
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should return the right class according to magic & opcode" do
|
103
|
+
@pkt[:magic].should == 0x81
|
104
|
+
@pkt[:opcode].should == 0
|
105
|
+
@pkt.class.should == Memcached::Response
|
106
|
+
end
|
107
|
+
it "should return the right data type" do
|
108
|
+
@pkt[:data_type].should == 0
|
109
|
+
end
|
110
|
+
it "should return the right status" do
|
111
|
+
@pkt[:status].should == Memcached::Errors::NO_ERROR
|
112
|
+
end
|
113
|
+
it "should return the right opaque" do
|
114
|
+
@pkt[:opaque].should == 0
|
115
|
+
end
|
116
|
+
it "should return the right CAS" do
|
117
|
+
@pkt[:cas].should == 1
|
118
|
+
end
|
119
|
+
it "should parse the body correctly" do
|
120
|
+
@pkt[:key].should == ""
|
121
|
+
@pkt[:value].should == "World"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: remcached
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Stephan Maka
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-12 00:00:00 +02:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Ruby EventMachine memcached client
|
17
|
+
email: astro@spaceboyz.net
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.rst
|
24
|
+
files:
|
25
|
+
- .gitignore
|
26
|
+
- README.rst
|
27
|
+
- Rakefile
|
28
|
+
- VERSION.yml
|
29
|
+
- examples/fill.rb
|
30
|
+
- lib/remcached.rb
|
31
|
+
- lib/remcached/client.rb
|
32
|
+
- lib/remcached/const.rb
|
33
|
+
- lib/remcached/pack_array.rb
|
34
|
+
- lib/remcached/packet.rb
|
35
|
+
- remcached.gemspec
|
36
|
+
- spec/client_spec.rb
|
37
|
+
- spec/memcached_spec.rb
|
38
|
+
- spec/packet_spec.rb
|
39
|
+
has_rdoc: true
|
40
|
+
homepage: http://github.com/astro/remcached/
|
41
|
+
licenses: []
|
42
|
+
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options:
|
45
|
+
- --charset=UTF-8
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: "0"
|
53
|
+
version:
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
version:
|
60
|
+
requirements: []
|
61
|
+
|
62
|
+
rubyforge_project:
|
63
|
+
rubygems_version: 1.3.5
|
64
|
+
signing_key:
|
65
|
+
specification_version: 3
|
66
|
+
summary: Ruby EventMachine memcached client
|
67
|
+
test_files:
|
68
|
+
- spec/client_spec.rb
|
69
|
+
- spec/memcached_spec.rb
|
70
|
+
- spec/packet_spec.rb
|
71
|
+
- examples/fill.rb
|