memcached 0.5

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.
@@ -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
+