hybrid_memcache 0.1.0

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/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Matthew Knopp
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,17 @@
1
+ = hybrid-memcache
2
+
3
+ ninjudd-memcache compatible interface on top of fauna-memcached
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
13
+ * Send me a pull request. Bonus points for topic branches.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2010 Matthew Knopp. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,54 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "hybrid_memcache"
8
+ gem.summary = %Q{ninjudd-memcached compatible interface to fauna-memcached}
9
+ gem.description = %Q{the summary says it all.}
10
+ gem.email = "mknopp@yammer-inc.com"
11
+ gem.homepage = "http://github.com/mhat/hybrid_memcache"
12
+ gem.authors = ["Matthew Knopp"]
13
+
14
+ gem.add_dependency "memcached", ">= 0.18.0"
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
+ end
21
+
22
+ require 'rake/testtask'
23
+ Rake::TestTask.new(:test) do |test|
24
+ test.libs << 'lib' << 'test'
25
+ test.pattern = 'test/**/test_*.rb'
26
+ test.verbose = true
27
+ end
28
+
29
+ begin
30
+ require 'rcov/rcovtask'
31
+ Rcov::RcovTask.new do |test|
32
+ test.libs << 'test'
33
+ test.pattern = 'test/**/test_*.rb'
34
+ test.verbose = true
35
+ end
36
+ rescue LoadError
37
+ task :rcov do
38
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
39
+ end
40
+ end
41
+
42
+ task :test => :check_dependencies
43
+
44
+ task :default => :test
45
+
46
+ require 'rake/rdoctask'
47
+ Rake::RDocTask.new do |rdoc|
48
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
49
+
50
+ rdoc.rdoc_dir = 'rdoc'
51
+ rdoc.title = "hybrid_memcache #{version}"
52
+ rdoc.rdoc_files.include('README*')
53
+ rdoc.rdoc_files.include('lib/**/*.rb')
54
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,58 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{hybrid_memcache}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Matthew Knopp"]
12
+ s.date = %q{2010-02-17}
13
+ s.description = %q{the summary says it all.}
14
+ s.email = %q{mknopp@yammer-inc.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "hybrid_memcache.gemspec",
27
+ "lib/hybrid_memcache.rb",
28
+ "test/helper.rb",
29
+ "test/memcache_test.rb",
30
+ "test/test_helper.rb",
31
+ "test/test_hybrid_memcache.rb"
32
+ ]
33
+ s.homepage = %q{http://github.com/mhat/hybrid_memcache}
34
+ s.rdoc_options = ["--charset=UTF-8"]
35
+ s.require_paths = ["lib"]
36
+ s.rubygems_version = %q{1.3.5}
37
+ s.summary = %q{ninjudd-memcached compatible interface to fauna-memcached}
38
+ s.test_files = [
39
+ "test/helper.rb",
40
+ "test/memcache_test.rb",
41
+ "test/test_helper.rb",
42
+ "test/test_hybrid_memcache.rb"
43
+ ]
44
+
45
+ if s.respond_to? :specification_version then
46
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
47
+ s.specification_version = 3
48
+
49
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
50
+ s.add_runtime_dependency(%q<memcached>, [">= 0.18.0"])
51
+ else
52
+ s.add_dependency(%q<memcached>, [">= 0.18.0"])
53
+ end
54
+ else
55
+ s.add_dependency(%q<memcached>, [">= 0.18.0"])
56
+ end
57
+ end
58
+
@@ -0,0 +1,362 @@
1
+ require 'memcached'
2
+
3
+ class Memcache < Memcached
4
+ WRITE_LOCK_WAIT = 1
5
+ LOCK_TIMEOUT = 5
6
+
7
+ def initialize (opts={})
8
+ servers = opts[:servers] && opts.delete(:servers)
9
+ @namespace = ""
10
+
11
+ super(servers, {
12
+ :prefix_key => '',
13
+ :prefix_delimiter => '',
14
+ :support_cas => true,
15
+
16
+ :hash => :fnv1_32,
17
+ :distribution => :consistent_ketama,
18
+ :ketama_weighted => true,
19
+ :server_failure_limit => 2,
20
+ :retry_timeout => 30,
21
+
22
+ :default_ttl => 604800,
23
+ }.merge!(opts))
24
+ end
25
+
26
+
27
+ def clone
28
+ klone = self.class.new({ :servers => servers }.merge(options))
29
+ klone.set_namespace @namespace
30
+ klone
31
+ end
32
+
33
+
34
+ def add(key, value, opts={})
35
+ super(normalize_keys(key), value, *opts_to_params(opts))
36
+ return value
37
+ rescue Memcached::NotStored
38
+ return nil
39
+ end
40
+
41
+
42
+ def inspect
43
+ "<Memcache: %d servers, ns: %p" % [ servers.length, namespace ]
44
+ end
45
+
46
+
47
+ def in_namespace(ns)
48
+ # Temporarily change the namespace for convenience.
49
+ begin
50
+ old_namespace = @namespace
51
+ self.set_namespace("#{old_namespace}#{ns}")
52
+ yield
53
+ ensure
54
+ self.set_namespace(old_namespace)
55
+ end
56
+ end
57
+
58
+
59
+ def namespace
60
+ @namespace
61
+ end
62
+
63
+
64
+ def set_namespace(ns)
65
+ @namespace = ns
66
+ end
67
+ alias namespace= set_namespace
68
+ alias set_namespace= set_namespace
69
+
70
+
71
+ def get(keys, opts={})
72
+ marshal = !opts[:raw]
73
+ cas = opts[:cas]
74
+
75
+ unless keys.is_a?(Array)
76
+ ## ninjudd-memcache has a weird behaviour where get can be called with an
77
+ ## expiry and that will transform the get into get+cas. this in turn has
78
+ ## the effect of extending the expiry of the object.
79
+ if opts[:expiry]
80
+ value = get(keys, :cas => true)
81
+ value = cas(keys, value, :cas => value.memcache_cas, :expiry => opts[:expiry])
82
+ return value
83
+ end
84
+
85
+ ## Single get
86
+ value, flags, ret = Lib.memcached_get_rvalue(@struct, normalize_keys(keys))
87
+
88
+ ## ninjudd-memcache treats broken servers as cache missis, so return nil
89
+ check_return_code(ret, keys)
90
+ return nil unless ret == 0
91
+
92
+ if marshal
93
+ value = Marshal.load(value)
94
+ end
95
+
96
+ value.memcache_cas = cas ? @struct.result.cas : false
97
+ value.memcache_flags = flags
98
+ return value
99
+ else
100
+ ## Multi get
101
+ return {} if keys.empty?
102
+
103
+ ## ninjudd-memcache normalizes keys into the form namespace:index:key
104
+ ## but it hides this form from the caller, so the caller expects to
105
+ ## get a hash with the keys in their denormalized form. That's what
106
+ ## the norm_to_std hash is all about.
107
+ normalized = normalize_keys(keys)
108
+ norm_to_std = {}
109
+
110
+ ## but note, the keys have to be transformed into strings, even if they
111
+ ## started out as fixnums
112
+ keys.each_with_index {|k,idx| norm_to_std[normalized[idx]] = keys[idx].to_s }
113
+
114
+ ret = Lib.memcached_mget(@struct, normalized)
115
+
116
+ ## once again: potentiall braken server == cache miss
117
+ check_return_code(ret, normalized)
118
+ return {} unless ret == 0
119
+
120
+ hash = {}
121
+ keys.each do
122
+ value, key, flags, ret = Lib.memcached_fetch_rvalue(@struct)
123
+ if ret == Lib::MEMCACHED_END
124
+ break
125
+ end
126
+ check_return_code(ret, key)
127
+
128
+ # Assign the value
129
+ if marshal
130
+ value = Marshal.load(value)
131
+ end
132
+ value.memcache_cas = cas ? @struct.result.cas : false
133
+ value.memcache_flags = flags
134
+
135
+ hash[ norm_to_std[key] ] = value
136
+ end
137
+ return hash
138
+ end
139
+ rescue Memcached::NotFound
140
+ return nil
141
+ end
142
+
143
+
144
+ def read(keys, opts={})
145
+ get(keys, opts.merge(:raw => true))
146
+ end
147
+
148
+
149
+ def set(key, value, opts={})
150
+ super(normalize_keys(key), value, *opts_to_params(opts))
151
+ return value
152
+ rescue Memcache::NotFound
153
+ return nil
154
+ end
155
+
156
+
157
+ def write(key, value, opts={})
158
+ set(key, value, opts.merge(:raw => true))
159
+ end
160
+
161
+
162
+ def replace(key, value, opts={})
163
+ super(normalize_keys(key), value, *opts_to_params(opts))
164
+ return value
165
+ rescue Memcache::NotStored
166
+ return nil
167
+ end
168
+
169
+
170
+ def cas(key, value, opts={})
171
+ ttl, marshal, flags = opts_to_params(opts)
172
+ key = normalize_keys(key)
173
+ data = marshal ? Marshal.dump(value) : value
174
+
175
+ check_return_code(Lib.memcached_cas(@struct, key, data, ttl, flags, opts[:cas]), key)
176
+ value.memcache_cas = @struct.result.cas
177
+ value.memcache_flags = @struct.result.flags
178
+ return value
179
+ rescue Memcache::NotStored
180
+ return nil
181
+ end
182
+
183
+
184
+ def prepend(key,value)
185
+ super(normalize_keys(key), value)
186
+ return true
187
+ rescue Memcache::NotStored
188
+ return false
189
+ end
190
+
191
+
192
+ def append(key, value)
193
+ super(normalize_keys(key), value)
194
+ return true
195
+ rescue Memcache::NotStored
196
+ return false
197
+ end
198
+
199
+
200
+ def count(key)
201
+ get(key, :raw => true).to_i
202
+ end
203
+
204
+
205
+ def increment(key, amount=1)
206
+ super(normalize_keys(key), amount)
207
+ rescue Memcache::NotStored
208
+ return nil
209
+ end
210
+ alias incr increment
211
+
212
+
213
+ def decrement(key, amount=1)
214
+ super(normalize_keys(key), amount)
215
+ rescue Memcache::NotStored
216
+ return nil
217
+ end
218
+ alias decr decrement
219
+
220
+
221
+ def update(key, opts={})
222
+ if value = get(key, :cas => true)
223
+ cas(key, yield(value), opts.merge!(:cas => value.memcache_cas))
224
+ else
225
+ add(key, yield(value), opts)
226
+ end
227
+ end
228
+
229
+
230
+ def get_or_add(key, *args)
231
+ if block_given?
232
+ opts = args[0] || {}
233
+ get(key) || add(key, yield, opts) || get(key)
234
+ else
235
+ opts = args[1] || {}
236
+ get(key) || add(key, args[0], opts) || get(key)
237
+ end
238
+ end
239
+
240
+
241
+ def get_or_set(key, *args)
242
+ if block_given?
243
+ opts = args[0] || {}
244
+ get(key) || set(key, yield, opts) || get(key)
245
+ else
246
+ opts = args[1] || {}
247
+ get(key) || set(key, args[0], opts) || get(key)
248
+ end
249
+ end
250
+
251
+
252
+ def get_some(keys, opts = {})
253
+ keys = keys.collect { |k| k.to_s }
254
+ records = opts[:disable] ? {} : self.get(keys, opts)
255
+
256
+ if opts[:validation]
257
+ records.delete_if do |key, value|
258
+ not opts[:validation].call(key, value)
259
+ end
260
+ end
261
+
262
+ keys_to_fetch = keys - records.keys
263
+ method = opts[:overwrite] ? :set : :add
264
+ if keys_to_fetch.any?
265
+ yield(keys_to_fetch).each do |key, value|
266
+ self.send(method, key, value, opts) unless opts[:disable] or opts[:disable_write]
267
+ records[key] = value
268
+ end
269
+ end
270
+ records
271
+ end
272
+
273
+
274
+ def lock_key(key)
275
+ "lock:#{key}"
276
+ end
277
+
278
+
279
+ def lock(key, opts={})
280
+ expiry = opts[:expiry] || LOCK_TIMEOUT
281
+ add(lock_key(key), Socket.gethostname, :expiry => expiry, :raw => true)
282
+ end
283
+
284
+
285
+ def unlock(key)
286
+ delete(lock_key(key))
287
+ end
288
+
289
+
290
+ def with_lock(key, opts={})
291
+ until lock(key) do
292
+ return if opts[:ignore]
293
+ sleep(WRITE_LOCK_WAIT)
294
+ end
295
+ yield
296
+ unlock(key) unless opts[:keep]
297
+ end
298
+
299
+
300
+ def locked?(key)
301
+ get(lock_key(key), :raw => true)
302
+ end
303
+
304
+
305
+ def delete(key)
306
+ super(normalize_keys(key))
307
+ return true
308
+ rescue Memcached::NotFound
309
+ return false
310
+ end
311
+
312
+
313
+ def flush_all(opts={})
314
+ flush
315
+ end
316
+ alias clear flush_all
317
+
318
+
319
+ def [](key)
320
+ get(key)
321
+ end
322
+
323
+
324
+ def []=(key,value)
325
+ set(key,value)
326
+ end
327
+
328
+
329
+ def normalize_keys (keys)
330
+ ns = @namespace.nil? || @namespace.size == 0 ? "" : "#{@namespace}:"
331
+
332
+ unless keys.is_a?(Array)
333
+ k = "#{ns}#{keys}"
334
+ k.gsub!(/%/, '%%') if k.include?('%')
335
+ k.gsub!(/ /, '%s') if k.include?(' ')
336
+ return k
337
+ else
338
+ return keys.collect do |k|
339
+ k = "#{ns}#{k}"
340
+ k.gsub!(/%/, '%%') if k.include?('%')
341
+ k.gsub!(/ /, '%s') if k.include?(' ')
342
+ k
343
+ end
344
+ end
345
+ end
346
+
347
+
348
+ def opts_to_params (opts={})
349
+ ## - if no :expiry, use @default_ttl
350
+ ## - fauna-memcached uses marshal which has the opposite meaning of
351
+ ## ninjudd's :raw. that is, :raw means DO NOT marshal, so invert
352
+ ## :raw via ! ...
353
+ ## - if no :flags, use FLAGS
354
+ return opts[:expiry] || @default_ttl,
355
+ !opts[:raw],
356
+ opts[:flags] || FLAGS
357
+ end
358
+ end
359
+
360
+ class Object
361
+ attr_accessor :memcache_flags, :memcache_cas
362
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'hybrid_memcache'
8
+
9
+ class Test::Unit::TestCase
10
+ end
@@ -0,0 +1,233 @@
1
+ require 'test/unit'
2
+ require File.dirname(__FILE__) + '/test_helper'
3
+
4
+ class MemcacheTest < Test::Unit::TestCase
5
+ PORTS = [11212, 11213, 11214, 11215, 11216, 11217]
6
+
7
+ def m
8
+ @memcache
9
+ end
10
+
11
+ def setup
12
+ start_memcache(*PORTS)
13
+ @memcache = Memcache.new(:servers => PORTS.collect {|p| "localhost:#{p}"})
14
+ end
15
+
16
+ def teardown
17
+ stop_memcache(*PORTS)
18
+ end
19
+
20
+ def test_get_and_set
21
+ 100.times do |i|
22
+ m.set(i.to_s, i)
23
+ assert_equal i, m.get(i.to_s)
24
+ end
25
+
26
+ keys = (0..200).to_a
27
+ results = m.get(keys)
28
+ assert_equal 100, results.size
29
+ results.each do |key, value|
30
+ assert_equal key.to_i, value
31
+ end
32
+
33
+ 100.times do |i|
34
+ m.set(i.to_s, i.to_s, :raw => true)
35
+ assert_equal i.to_s, m.get(i.to_s, :raw => true)
36
+ end
37
+
38
+ results = m.get(keys ,:raw => true)
39
+ assert_equal 100, results.size
40
+ results.each do |key, value|
41
+ assert_equal key, value
42
+ end
43
+ end
44
+
45
+ def test_alternate_accessors
46
+ m['baz'] = 24
47
+ assert_equal 24, m['baz']
48
+ end
49
+
50
+ def test_add_and_replace
51
+ 100.times do |i|
52
+ m.replace(i.to_s, [:foo, i])
53
+ assert_equal nil, m.get(i.to_s)
54
+
55
+ m.add(i.to_s, [:bar, i])
56
+ assert_equal [:bar, i], m.get(i.to_s)
57
+
58
+ m.replace(i.to_s, [:foo, i])
59
+ assert_equal [:foo, i], m.get(i.to_s)
60
+
61
+ m.add(i.to_s, [:baz, i])
62
+ assert_equal [:foo, i], m.get(i.to_s)
63
+
64
+ m.replace(i.to_s, 'blah', :raw => true)
65
+ assert_equal 'blah', m.get(i.to_s, :raw => true)
66
+
67
+ m.delete(i.to_s)
68
+ assert_equal nil, m.get(i.to_s, :raw => true)
69
+
70
+ m.add(i.to_s, 'homerun', :raw => true)
71
+ assert_equal 'homerun', m.get(i.to_s, :raw => true)
72
+ end
73
+ end
74
+
75
+ def test_append_and_prepend
76
+ 100.times do |i|
77
+ m.append(i.to_s, 'doh!')
78
+ assert_equal nil, m.read(i.to_s)
79
+
80
+ m.write(i.to_s, 'bar')
81
+ m.prepend(i.to_s, 'foo')
82
+ assert_equal 'foobar', m.read(i.to_s)
83
+
84
+ m.append(i.to_s, i.to_s)
85
+ assert_equal "foobar#{i}", m.read(i.to_s)
86
+ end
87
+ end
88
+
89
+ def test_get_or_set
90
+ 100.times do |i|
91
+ m.get_or_set("foo#{i}", [i, :foo])
92
+ assert_equal [i, :foo], m["foo#{i}"]
93
+
94
+ m.get_or_set("foo#{i}") {raise}
95
+ assert_equal [i, :foo], m["foo#{i}"]
96
+
97
+ # Overwrite if changed.
98
+ m.get_or_set("bar#{i}") do
99
+ m.set("bar#{i}", [i, :foo])
100
+ [i, :bar]
101
+ end
102
+ assert_equal [i, :bar], m["bar#{i}"]
103
+ end
104
+ end
105
+
106
+ def test_get_or_add
107
+ 100.times do |i|
108
+ m.get_or_add("foo#{i}", [:foo, i])
109
+ assert_equal [:foo, i], m["foo#{i}"]
110
+
111
+ m.get_or_add("foo#{i}") {raise}
112
+ assert_equal [:foo, i], m["foo#{i}"]
113
+
114
+ # Don't overwrite if changed.
115
+ m.get_or_add("bar#{i}") do
116
+ m.set("bar#{i}", [:foo, i])
117
+ :bar
118
+ end
119
+ assert_equal [:foo, i], m["bar#{i}"]
120
+ end
121
+ end
122
+
123
+ def test_update
124
+ 100.times do |i|
125
+ m.set("foo#{i}", [:foo, i])
126
+ assert_equal [:foo, i], m["foo#{i}"]
127
+
128
+ m.update("foo#{i}") do |list|
129
+ list << i.to_s
130
+ list << :bar
131
+ list
132
+ end
133
+ assert_equal [:foo, i, i.to_s, :bar], m["foo#{i}"]
134
+ end
135
+ end
136
+
137
+ def test_get_some
138
+ 100.times do |i|
139
+ i = i * 2
140
+ m.set(i.to_s, i)
141
+ assert_equal i, m.get(i.to_s)
142
+ end
143
+
144
+ keys = (0...200).to_a
145
+ results = m.get_some(keys) do |missing_keys|
146
+ assert_equal 100, missing_keys.size
147
+ r = {}
148
+ missing_keys.each do |key|
149
+ r[key] = key.to_i
150
+ end
151
+ r
152
+ end
153
+
154
+ assert_equal 200, results.size
155
+ results.each do |key, value|
156
+ assert_equal key.to_i, value
157
+ end
158
+ end
159
+
160
+ def test_get_with_reset_expiry
161
+ m.add('foo', 'quick brown fox', :expiry => 1)
162
+ assert_equal 'quick brown fox', m.get('foo', :expiry => 2)
163
+ sleep(1)
164
+ assert_equal 'quick brown fox', m.get('foo')
165
+ end
166
+
167
+ def test_expiry
168
+ 100.times do |i|
169
+ m.set("int#{i}", i, :expiry => 1)
170
+ assert_equal i, m.get("int#{i}")
171
+
172
+ m.set("time#{i}", i, :expiry => Time.now + 1)
173
+ assert_equal i, m.get("time#{i}")
174
+ end
175
+
176
+ sleep 2
177
+
178
+ 100.times do |i|
179
+ assert_equal nil, m.get("int#{i}")
180
+ assert_equal nil, m.get("time#{i}")
181
+ end
182
+ end
183
+
184
+ def test_in_namespace
185
+ threads = []
186
+ 10.times do |i|
187
+ m.in_namespace("_#{i}_") do
188
+ 10.times do |j|
189
+ m.in_namespace("_#{j}_") do
190
+ assert_equal nil, m.get('foo')
191
+ m.set('foo', 'bar')
192
+ assert_equal 'bar', m.get('foo')
193
+ end
194
+ end
195
+ end
196
+ end
197
+ end
198
+
199
+ def test_incr_and_decr
200
+ m.write('foo', 0)
201
+
202
+ m.incr('foo', 100)
203
+ assert_equal 100, m.count('foo')
204
+
205
+ m.decr('foo', 100)
206
+ assert_equal 0, m.count('foo')
207
+
208
+ m.incr('foo', 500)
209
+ assert_equal 500, m.count('foo')
210
+
211
+ m.decr('foo', 300)
212
+ assert_equal 200, m.count('foo')
213
+
214
+ m.decr('foo', 300)
215
+ assert_equal 0, m.count('foo')
216
+ end
217
+
218
+ def test_flags
219
+ m.set('foo', :foo, :flags => 43)
220
+ assert_equal 43, m.get('foo').memcache_flags
221
+
222
+ m.set('foo', 'foo', :raw => true, :flags => 43)
223
+ assert_equal 43, m.get('foo', :raw => true).memcache_flags
224
+ end
225
+
226
+ def test_clone
227
+ m.set('foo', 1)
228
+ c = m.clone
229
+
230
+ #assert_not_equal m.servers.collect {|s| s.send(:socket)},
231
+ # c.servers.collect {|s| s.send(:socket)}
232
+ end
233
+ end
@@ -0,0 +1,22 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib')
2
+ require 'rubygems'
3
+ require 'hybrid_memcache'
4
+
5
+ system 'killall memcached 2> /dev/null'
6
+
7
+ class Test::Unit::TestCase
8
+ def start_memcache(*ports)
9
+ ports.each do |port|
10
+ system("memcached -p #{port} -U 0 -d -P /tmp/memcached_#{port}.pid")
11
+ end
12
+ sleep 0.1
13
+ end
14
+
15
+ def stop_memcache(*ports)
16
+ ports.each do |port|
17
+ pid = File.read("/tmp/memcached_#{port}.pid").to_i
18
+ Process.kill('TERM', pid)
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ class TestFaunaNinjuddHybridMemcache < Test::Unit::TestCase
4
+ should "probably rename this file and start testing for real" do
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hybrid_memcache
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Matthew Knopp
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-02-17 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: memcached
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.18.0
24
+ version:
25
+ description: the summary says it all.
26
+ email: mknopp@yammer-inc.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - LICENSE
33
+ - README.rdoc
34
+ files:
35
+ - .document
36
+ - .gitignore
37
+ - LICENSE
38
+ - README.rdoc
39
+ - Rakefile
40
+ - VERSION
41
+ - hybrid_memcache.gemspec
42
+ - lib/hybrid_memcache.rb
43
+ - test/helper.rb
44
+ - test/memcache_test.rb
45
+ - test/test_helper.rb
46
+ - test/test_hybrid_memcache.rb
47
+ has_rdoc: true
48
+ homepage: http://github.com/mhat/hybrid_memcache
49
+ licenses: []
50
+
51
+ post_install_message:
52
+ rdoc_options:
53
+ - --charset=UTF-8
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ version:
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: "0"
67
+ version:
68
+ requirements: []
69
+
70
+ rubyforge_project:
71
+ rubygems_version: 1.3.5
72
+ signing_key:
73
+ specification_version: 3
74
+ summary: ninjudd-memcached compatible interface to fauna-memcached
75
+ test_files:
76
+ - test/helper.rb
77
+ - test/memcache_test.rb
78
+ - test/test_helper.rb
79
+ - test/test_hybrid_memcache.rb