memcached 0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+
2
+ require 'libmemcached'
3
+ require 'memcached/integer'
4
+ require 'memcached/exceptions'
5
+ require 'memcached/behaviors'
6
+ require 'memcached/memcached'
@@ -0,0 +1,46 @@
1
+
2
+ class Memcached
3
+
4
+ def self.load_constants(prefix, hash = {}, offset = 0)
5
+ Libmemcached.constants.grep(/^#{prefix}/).each do |const_name|
6
+ hash[const_name[prefix.length..-1].downcase.to_sym] = Libmemcached.const_get(const_name) + offset
7
+ end
8
+ hash
9
+ end
10
+
11
+ BEHAVIORS = load_constants("MEMCACHED_BEHAVIOR_")
12
+
13
+ BEHAVIOR_VALUES = {
14
+ false => 0,
15
+ true => 1
16
+ }
17
+
18
+ HASH_VALUES = {}
19
+ BEHAVIOR_VALUES.merge!(load_constants("MEMCACHED_HASH_", HASH_VALUES, 2))
20
+
21
+ DISTRIBUTION_VALUES = {}
22
+ BEHAVIOR_VALUES.merge!(load_constants("MEMCACHED_DISTRIBUTION_", DISTRIBUTION_VALUES, 2))
23
+
24
+ private
25
+
26
+ def set_behavior(behavior, value)
27
+ raise ArgumentError, "No setting #{behavior.inspect}" unless b_id = BEHAVIORS[behavior]
28
+ raise ArgumentError, "No setting value #{value.inspect}" unless v_id = BEHAVIOR_VALUES[value]
29
+
30
+ # Scoped validations
31
+ msg = "Invalid setting value #{value.inspect} for #{behavior.inspect}"
32
+ if behavior == :hash
33
+ raise ArgumentError, msg unless HASH_VALUES[value]
34
+ elsif behavior == :distribution
35
+ raise ArgumentError, msg unless DISTRIBUTION_VALUES[value]
36
+ end
37
+ # STDERR.puts "Setting #{behavior}:#{b_id} => #{value}:#{v_id}"
38
+
39
+ unless value == false
40
+ # XXX Setting false still turns on the behavior; maybe a Libmemcached bug
41
+ Libmemcached.memcached_behavior_set(@struct, b_id, v_id)
42
+ end
43
+
44
+ end
45
+
46
+ end
@@ -0,0 +1,30 @@
1
+
2
+ class Memcached
3
+
4
+ class Error < RuntimeError
5
+ end
6
+
7
+ class NotImplemented < StandardError
8
+ end
9
+
10
+ class << self
11
+ private
12
+ def camelize(string)
13
+ string.downcase.split(' ').map {|s| s.capitalize}.join
14
+ end
15
+ end
16
+
17
+ @@exceptions = []
18
+ @@empty_struct = Libmemcached::MemcachedSt.new
19
+ Libmemcached.memcached_create(@@empty_struct)
20
+
21
+ # Generate exception classes
22
+ Libmemcached::MEMCACHED_MAXIMUM_RETURN.times do |exception_index|
23
+ description = Libmemcached.memcached_strerror(@@empty_struct, exception_index)
24
+ exception_class = eval("class #{camelize(description)} < Error; self; end")
25
+ @@exceptions << exception_class
26
+ end
27
+
28
+ # Verify library version
29
+ # XXX Impossible with current libmemcached
30
+ end
@@ -0,0 +1,6 @@
1
+
2
+ class Integer
3
+ def tv_sec
4
+ self
5
+ end
6
+ end
@@ -0,0 +1,218 @@
1
+
2
+ class Memcached
3
+
4
+ FLAGS = 0x0
5
+
6
+ DEFAULTS = {
7
+ :hash => :default,
8
+ :distribution => :consistent,
9
+ :buffer_requests => false,
10
+ :support_cas => false,
11
+ :tcp_nodelay => false,
12
+ :no_block => false
13
+ }
14
+
15
+ IGNORED = 0
16
+
17
+ attr_reader :namespace
18
+ attr_reader :options
19
+
20
+ ### Configuration
21
+
22
+ def initialize(servers, opts = {})
23
+ @struct = Libmemcached::MemcachedSt.new
24
+ Libmemcached.memcached_create(@struct)
25
+
26
+ # Servers
27
+ Array(servers).each_with_index do |server, index|
28
+ unless server.is_a? String and server =~ /^(\d{1,3}\.){3}\d{1,3}:\d{1,5}$/
29
+ raise ArgumentError, "Servers must be in the format ip:port (e.g., '127.0.0.1:11211')"
30
+ end
31
+ host, port = server.split(":")
32
+ Libmemcached.memcached_server_add(@struct, host, port.to_i)
33
+
34
+ # XXX To be removed once Krow fixes the write_ptr bug
35
+ Libmemcached.memcached_repair_server_st(@struct,
36
+ Libmemcached.memcached_select_server_at(@struct, index)
37
+ )
38
+ end
39
+
40
+ # Namespace
41
+ @namespace = opts[:namespace]
42
+ raise ArgumentError, "Invalid namespace" if namespace.to_s =~ / /
43
+
44
+ # Behaviors
45
+ @options = DEFAULTS.merge(opts)
46
+ options.each do |option, value|
47
+ set_behavior(option, value) unless option == :namespace
48
+ end
49
+ end
50
+
51
+ def servers
52
+ server_structs.map do |server|
53
+ "#{server.hostname}:#{server.port}"
54
+ end
55
+ end
56
+
57
+ def clone
58
+ # XXX Could be more efficient if we used Libmemcached.memcached_clone(@struct)
59
+ self.class.new(servers, options)
60
+ end
61
+
62
+ alias :dup :clone
63
+
64
+ ### Configuration helpers
65
+
66
+ private
67
+
68
+ def server_structs
69
+ array = []
70
+ @struct.hosts.count.times do |i|
71
+ array << Libmemcached.memcached_select_server_at(@struct, i)
72
+ end
73
+ array
74
+ end
75
+
76
+ ### Operations
77
+
78
+ public
79
+
80
+ # Setters
81
+
82
+ def set(key, value, timeout=0, marshal=true)
83
+ value = marshal ? Marshal.dump(value) : value.to_s
84
+ check_return_code(
85
+ Libmemcached.memcached_set(@struct, ns(key), value, timeout, FLAGS)
86
+ )
87
+ end
88
+
89
+ def add(key, value, timeout=0, marshal=true)
90
+ value = marshal ? Marshal.dump(value) : value.to_s
91
+ check_return_code(
92
+ Libmemcached.memcached_add(@struct, ns(key), value, timeout, FLAGS)
93
+ )
94
+ end
95
+
96
+ def increment(key, offset=1)
97
+ ret, value = Libmemcached.memcached_increment(@struct, ns(key), offset)
98
+ check_return_code(ret)
99
+ value
100
+ end
101
+
102
+ def decrement(key, offset=1)
103
+ ret, value = Libmemcached.memcached_decrement(@struct, ns(key), offset)
104
+ check_return_code(ret)
105
+ value
106
+ end
107
+
108
+ alias :incr :increment
109
+ alias :decr :decrement
110
+
111
+ def replace(key, value, timeout=0, marshal=true)
112
+ value = marshal ? Marshal.dump(value) : value.to_s
113
+ check_return_code(
114
+ Libmemcached.memcached_replace(@struct, ns(key), value, timeout, FLAGS)
115
+ )
116
+ end
117
+
118
+ def append(key, value)
119
+ # Requires memcached 1.2.4
120
+ check_return_code(
121
+ Libmemcached.memcached_append(@struct, ns(key), value.to_s, IGNORED, FLAGS)
122
+ )
123
+ end
124
+
125
+ def prepend(key, value)
126
+ # Requires memcached 1.2.4
127
+ check_return_code(
128
+ Libmemcached.memcached_prepend(@struct, ns(key), value.to_s, IGNORED, FLAGS)
129
+ )
130
+ end
131
+
132
+ def cas
133
+ # Requires memcached HEAD
134
+ raise NotImplemented
135
+ raise "CAS not enabled" unless options[:support_cas]
136
+ end
137
+
138
+ # Deleters
139
+
140
+ def delete(key, timeout=0)
141
+ check_return_code(
142
+ Libmemcached.memcached_delete(@struct, ns(key), timeout)
143
+ )
144
+ end
145
+
146
+ # Getters
147
+
148
+ def get(key, marshal=true)
149
+ if key.is_a? Array
150
+ # Multi get
151
+ # XXX Waiting on the real implementation
152
+ key.map do |this_key|
153
+ begin
154
+ get(this_key, marshal)
155
+ rescue NotFound
156
+ # XXX Not sure how this behavior should be defined
157
+ end
158
+ end
159
+ else
160
+ # Single get
161
+ # XXX Server doesn't validate. Possibly a performance problem.
162
+ raise ClientError, "Invalid key" if !key.is_a? String or key =~ /\s/
163
+
164
+ value, flags, ret = Libmemcached.memcached_get_ruby_string(@struct, ns(key))
165
+ check_return_code(ret)
166
+ value = Marshal.load(value) if marshal
167
+ value
168
+ end
169
+ end
170
+
171
+ # Information methods
172
+
173
+ def stats
174
+ stats = Hash.new([])
175
+
176
+ stat_struct, ret = Libmemcached.memcached_stat(@struct, "")
177
+ check_return_code(ret)
178
+
179
+ keys, ret = Libmemcached.memcached_stat_get_keys(@struct, stat_struct)
180
+ check_return_code(ret)
181
+
182
+ keys.each do |key|
183
+ server_structs.size.times do |index|
184
+
185
+ value, ret = Libmemcached.memcached_stat_get_value(
186
+ @struct,
187
+ Libmemcached.memcached_select_stat_at(@struct, stat_struct, index),
188
+ key)
189
+ check_return_code(ret)
190
+
191
+ value = case value
192
+ when /^\d+\.\d+$/: value.to_f
193
+ when /^\d+$/: value.to_i
194
+ else value
195
+ end
196
+
197
+ stats[key.to_sym] += [value]
198
+ end
199
+ end
200
+
201
+ Libmemcached.memcached_stat_free(@struct, stat_struct)
202
+ stats
203
+ end
204
+
205
+ ### Operations helpers
206
+
207
+ private
208
+
209
+ def ns(key)
210
+ "#{@namespace}#{key}"
211
+ end
212
+
213
+ def check_return_code(ret)
214
+ return if ret == 0
215
+ raise @@exceptions[ret]
216
+ end
217
+
218
+ end
@@ -0,0 +1,38 @@
1
+
2
+ # Gem::Specification for Memcached-0.5
3
+ # Originally generated by Echoe
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = %q{memcached}
7
+ s.version = "0.5"
8
+
9
+ s.specification_version = 2 if s.respond_to? :specification_version=
10
+
11
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
+ s.authors = ["Evan Weaver"]
13
+ s.date = %q{2008-01-20}
14
+ s.description = %q{An interface to the libmemcached C client.}
15
+ s.email = %q{}
16
+ s.extensions = ["ext/extconf.rb"]
17
+ s.files = ["CHANGELOG", "ext/extconf.rb", "ext/libmemcached.h", "ext/libmemcached.i", "ext/libmemcached_wrap.c", "lib/memcached/behaviors.rb", "lib/memcached/exceptions.rb", "lib/memcached/integer.rb", "lib/memcached/memcached.rb", "lib/memcached.rb", "LICENSE", "Manifest", "README", "test/benchmark/benchmark_test.rb", "test/setup.rb", "test/teardown.rb", "test/test_helper.rb", "test/unit/binding_test.rb", "test/unit/memcached_test.rb", "memcached.gemspec"]
18
+ s.has_rdoc = true
19
+ s.homepage = %q{http://blog.evanweaver.com/files/doc/fauna/memcached/}
20
+ s.require_paths = ["lib", "ext"]
21
+ s.rubyforge_project = %q{fauna}
22
+ s.rubygems_version = %q{1.0.1}
23
+ s.summary = %q{An interface to the libmemcached C client.}
24
+ s.test_files = ["test/benchmark/benchmark_test.rb", "test/test_helper.rb", "test/unit/binding_test.rb", "test/unit/memcached_test.rb"]
25
+ end
26
+
27
+
28
+ # # Original Rakefile source (requires the Echoe gem):
29
+ #
30
+ # require 'echoe'
31
+ #
32
+ # Echoe.new("memcached") do |p|
33
+ # p.author = "Evan Weaver"
34
+ # p.project = "fauna"
35
+ # p.summary = "An interface to the libmemcached C client."
36
+ # p.url = "http://blog.evanweaver.com/files/doc/fauna/memcached/"
37
+ # p.docs_host = "blog.evanweaver.com:~/www/bax/public/files/doc/"
38
+ # end
@@ -0,0 +1,42 @@
1
+
2
+ require "#{File.dirname(__FILE__)}/../test_helper"
3
+
4
+ require 'rubygems'
5
+ require 'benchmark/unit'
6
+ require 'memcache'
7
+
8
+ class BenchmarkTest < Test::Unit::TestCase
9
+
10
+ def setup
11
+ @value = OpenStruct.new(:a => 1, :b => 2, :c => GenericClass)
12
+ @opts = [
13
+ ['127.0.0.1:43042', '127.0.0.1:43043'],
14
+ {:namespace => "benchmark_namespace"}
15
+ ]
16
+ end
17
+
18
+ def test_original_speed
19
+ @cache = MemCache.new(*@opts)
20
+ assert_faster(0.02) do
21
+ @cache.set 'key1', @value
22
+ @cache.get 'key1'
23
+ @cache.set 'key2', @value
24
+ @cache.set 'key3', @value
25
+ @cache.get 'key2'
26
+ @cache.get 'key3'
27
+ end
28
+ end
29
+
30
+ def test_new_speed
31
+ @cache = Memcached.new(*@opts)
32
+ assert_faster(0.005) do
33
+ @cache.set 'key1', @value
34
+ @cache.get 'key1'
35
+ @cache.set 'key2', @value
36
+ @cache.set 'key3', @value
37
+ @cache.get 'key2'
38
+ @cache.get 'key3'
39
+ end
40
+ end
41
+
42
+ end
@@ -0,0 +1,14 @@
1
+
2
+ # Start memcached
3
+
4
+ HERE = File.dirname(__FILE__)
5
+
6
+ `ps awx`.split("\n").grep(/4304[1-3]/).map do |process|
7
+ system("kill -9 #{process.to_i}")
8
+ end
9
+
10
+ log = "#{HERE}/log/memcached.log"
11
+ system "touch #{log}"
12
+
13
+ system "memcached -vv -p 43042 >> #{log} 2>&1 &"
14
+ system "memcached -vv -p 43043 >> #{log} 2>&1 &"
File without changes
@@ -0,0 +1,19 @@
1
+
2
+ $LOAD_PATH << "#{File.dirname(__FILE__)}/../lib"
3
+
4
+ if ENV['DEBUG']
5
+ require 'rubygems'
6
+ require 'ruby-debug'
7
+ end
8
+
9
+ require 'memcached'
10
+ require 'test/unit'
11
+ require 'ostruct'
12
+
13
+ class GenericClass
14
+ end
15
+
16
+ class Memcached
17
+ class StubError < Error
18
+ end
19
+ end
@@ -0,0 +1,8 @@
1
+
2
+ require "#{File.dirname(__FILE__)}/../test_helper"
3
+
4
+ class BindingTest < Test::Unit::TestCase
5
+ def test_libmemcached_loaded
6
+ assert_nothing_raised { Libmemcached }
7
+ end
8
+ end
@@ -0,0 +1,325 @@
1
+
2
+ require "#{File.dirname(__FILE__)}/../test_helper"
3
+
4
+ class MemcachedTest < Test::Unit::TestCase
5
+
6
+ def setup
7
+ @servers = ['127.0.0.1:43042', '127.0.0.1:43043']
8
+ @cache = Memcached.new(
9
+ @servers,
10
+ :namespace => 'class_test_namespace'
11
+ )
12
+ @value = OpenStruct.new(:a => 1, :b => 2, :c => GenericClass)
13
+ @marshalled_value = Marshal.dump(@value)
14
+ end
15
+
16
+ def test_initialize
17
+ cache = Memcached.new @servers, :namespace => 'test'
18
+ assert_equal 'test', cache.namespace
19
+ assert_equal 2, cache.send(:server_structs).size
20
+ assert_equal '127.0.0.1', cache.send(:server_structs).first.hostname
21
+ assert_equal '127.0.0.1', cache.send(:server_structs).last.hostname
22
+ assert_equal 43043, cache.send(:server_structs).last.port
23
+ end
24
+
25
+ def test_initialize_with_invalid_server_strings
26
+ assert_raise(ArgumentError) { Memcached.new "localhost:43042" }
27
+ assert_raise(ArgumentError) { Memcached.new "127.0.0.1:memcached" }
28
+ assert_raise(ArgumentError) { Memcached.new "127.0.0.1:43043:1" }
29
+ end
30
+
31
+ def test_initialize_without_namespace
32
+ cache = Memcached.new @servers
33
+ assert_equal nil, cache.namespace
34
+ assert_equal 2, cache.send(:server_structs).size
35
+ end
36
+
37
+ def test_initialize_positive_behavior
38
+ cache = Memcached.new @servers,
39
+ :buffer_requests => true
40
+ assert_raise(Memcached::ActionQueued) do
41
+ cache.set key, @value
42
+ end
43
+ end
44
+
45
+ def test_initialize_negative_behavior
46
+ cache = Memcached.new @servers,
47
+ :buffer_requests => false
48
+ assert_nothing_raised do
49
+ cache.set key, @value
50
+ end
51
+ end
52
+
53
+ def test_initialize_single_server
54
+ cache = Memcached.new '127.0.0.1:43042'
55
+ assert_equal nil, cache.namespace
56
+ assert_equal 1, cache.send(:server_structs).size
57
+ end
58
+
59
+ def test_initialize_strange_argument
60
+ assert_raise(ArgumentError) { Memcached.new 1 }
61
+ end
62
+
63
+ def test_get
64
+ @cache.set key, @value
65
+ result = @cache.get key
66
+ assert_equal @value, result
67
+ end
68
+
69
+ def test_get_with_namespace
70
+ @cache.set key, @value
71
+ result = @cache.get key, false
72
+ direct_result = Libmemcached.memcached_get(
73
+ @cache.instance_variable_get("@struct"),
74
+ "#{@cache.namespace}#{key}"
75
+ ).first
76
+ assert_equal result, direct_result
77
+ end
78
+
79
+ def test_get_nil
80
+ @cache.set key, nil, 0
81
+ result = @cache.get key
82
+ assert_equal nil, result
83
+ end
84
+
85
+ def test_get_missing
86
+ @cache.delete key rescue nil
87
+ assert_raise(Memcached::NotFound) do
88
+ result = @cache.get key
89
+ end
90
+ end
91
+
92
+ def test_truncation_issue_is_covered
93
+ value = OpenStruct.new(:a => Object.new) # Marshals with a null \000
94
+ @cache.set key, value
95
+ result = @cache.get key, false
96
+ non_wrapped_result = Libmemcached.memcached_get(
97
+ @cache.instance_variable_get("@struct"),
98
+ "#{@cache.namespace}#{key}"
99
+ ).first
100
+ assert result.size > non_wrapped_result.size
101
+ end
102
+
103
+ def test_get_invalid_key
104
+ assert_raise(Memcached::ClientError) { @cache.get(key * 100) }
105
+ assert_raise(Memcached::ClientError) { @cache.get "I'm so bad" }
106
+ end
107
+
108
+ def test_get_multi
109
+ @cache.set "#{key}_1", 1
110
+ @cache.set "#{key}_2", 2
111
+ assert_equal [1, 2],
112
+ @cache.get(["#{key}_1", "#{key}_2"])
113
+ end
114
+
115
+ def test_set_and_get_unmarshalled
116
+ @cache.set key, @value
117
+ result = @cache.get key, false
118
+ assert_equal @marshalled_value, result
119
+ end
120
+
121
+ def test_set
122
+ assert_nothing_raised do
123
+ @cache.set(key, @value)
124
+ end
125
+ end
126
+
127
+ def test_set_invalid_key
128
+ assert_raise(Memcached::ProtocolError) do
129
+ @cache.set "I'm so bad", @value
130
+ end
131
+ end
132
+
133
+ def test_set_expiry
134
+ @cache.set key, @value, 1
135
+ assert_nothing_raised do
136
+ @cache.get key
137
+ end
138
+ sleep(1)
139
+ assert_raise(Memcached::NotFound) do
140
+ @cache.get key
141
+ end
142
+ end
143
+
144
+ def test_set_object_too_large
145
+ assert_raise(Memcached::ServerError) do
146
+ @cache.set key, "I'm big" * 1000000
147
+ end
148
+ end
149
+
150
+ def test_delete
151
+ @cache.set key, @value
152
+ @cache.delete key
153
+ assert_raise(Memcached::NotFound) do
154
+ @cache.get key
155
+ end
156
+ end
157
+
158
+ def test_missing_delete
159
+ @cache.delete key rescue nil
160
+ assert_raise(Memcached::NotFound) do
161
+ @cache.delete key
162
+ end
163
+ end
164
+
165
+ def test_add
166
+ @cache.delete key rescue nil
167
+ @cache.add key, @value
168
+ assert_equal @value, @cache.get(key)
169
+ end
170
+
171
+ def test_existing_add
172
+ @cache.set key, @value
173
+ assert_raise(Memcached::NotStored) do
174
+ @cache.add key, @value
175
+ end
176
+ end
177
+
178
+ def test_add_expiry
179
+ @cache.delete key rescue nil
180
+ @cache.set key, @value, 1
181
+ assert_nothing_raised do
182
+ @cache.get key
183
+ end
184
+ sleep(1)
185
+ assert_raise(Memcached::NotFound) do
186
+ @cache.get key
187
+ end
188
+ end
189
+
190
+ def test_unmarshalled_add
191
+ @cache.delete key rescue nil
192
+ @cache.add key, @marshalled_value, 0, false
193
+ assert_equal @marshalled_value, @cache.get(key, false)
194
+ assert_equal @value, @cache.get(key)
195
+ end
196
+
197
+ def test_increment
198
+ @cache.set key, 10, 0, false
199
+ assert_equal 11, @cache.increment(key)
200
+ end
201
+
202
+ def test_increment_offset
203
+ @cache.set key, 10, 0, false
204
+ assert_equal 15, @cache.increment(key, 5)
205
+ end
206
+
207
+ def test_missing_increment
208
+ # XXX Fails due to libmemcached bug
209
+ @cache.delete key rescue nil
210
+ assert_raise(Memcached::NotFound) do
211
+ @cache.increment key
212
+ end
213
+ end
214
+
215
+ def test_decrement
216
+ @cache.set key, 10, 0, false
217
+ assert_equal 9, @cache.decrement(key)
218
+ end
219
+
220
+ def test_decrement_offset
221
+ @cache.set key, 10, 0, false
222
+ assert_equal 5, @cache.decrement(key, 5)
223
+ end
224
+
225
+ def test_missing_decrement
226
+ # XXX Fails due to libmemcached bug
227
+ @cache.delete key rescue nil
228
+ assert_raise(Memcached::NotFound) do
229
+ @cache.decrement key
230
+ end
231
+ end
232
+
233
+ def test_replace
234
+ @cache.set key, nil
235
+ assert_nothing_raised do
236
+ @cache.replace key, @value
237
+ end
238
+ assert_equal @value, @cache.get(key)
239
+ end
240
+
241
+ def test_missing_replace
242
+ @cache.delete key rescue nil
243
+ assert_raise(Memcached::NotStored) do
244
+ @cache.replace key, @value
245
+ end
246
+ assert_raise(Memcached::NotFound) do
247
+ assert_equal @value, @cache.get(key)
248
+ end
249
+ end
250
+
251
+ def test_append
252
+ @cache.set key, "start", 0, false
253
+ assert_nothing_raised do
254
+ @cache.append key, "end"
255
+ end
256
+ assert_equal "startend", @cache.get(key, false)
257
+ end
258
+
259
+ def test_missing_append
260
+ @cache.delete key rescue nil
261
+ assert_raise(Memcached::NotStored) do
262
+ @cache.append key, "end"
263
+ end
264
+ assert_raise(Memcached::NotFound) do
265
+ assert_equal @value, @cache.get(key)
266
+ end
267
+ end
268
+
269
+ def test_prepend
270
+ @cache.set key, "end", 0, false
271
+ assert_nothing_raised do
272
+ @cache.prepend key, "start"
273
+ end
274
+ assert_equal "startend", @cache.get(key, false)
275
+ end
276
+
277
+ def test_missing_prepend
278
+ @cache.delete key rescue nil
279
+ assert_raise(Memcached::NotStored) do
280
+ @cache.prepend key, "end"
281
+ end
282
+ assert_raise(Memcached::NotFound) do
283
+ assert_equal @value, @cache.get(key)
284
+ end
285
+ end
286
+
287
+ def test_cas
288
+ # XXX Not implemented
289
+ end
290
+
291
+ def test_stats
292
+ stats = @cache.stats
293
+ assert_equal 2, stats[:pid].size
294
+ assert_instance_of Fixnum, stats[:pid].first
295
+ assert_instance_of String, stats[:version].first
296
+ end
297
+
298
+ def test_clone
299
+ cache = @cache.clone
300
+ assert_equal cache.servers, @cache.servers
301
+ assert_not_equal cache, @cache
302
+ end
303
+
304
+ def test_thread_contention
305
+ threads = []
306
+ 4.times do |index|
307
+ threads << Thread.new do
308
+ cache = @cache.clone
309
+ assert_nothing_raised do
310
+ cache.set("test_thread_contention_#{index}", index)
311
+ end
312
+ assert_equal index, cache.get("test_thread_contention_#{index}")
313
+ end
314
+ end
315
+ threads.each {|thread| thread.join}
316
+ end
317
+
318
+ private
319
+
320
+ def key
321
+ caller.first[/`(.*)'/, 1]
322
+ end
323
+
324
+ end
325
+