simredis 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. data/README.rdoc +58 -0
  2. data/Rakefile +14 -0
  3. data/VERSION +1 -0
  4. data/simredis.gemspec +41 -0
  5. data/simredis.rb +597 -0
  6. metadata +59 -0
data/README.rdoc ADDED
@@ -0,0 +1,58 @@
1
+ = SimRedis (Pure Ruby Redis simulator for redis-rb)
2
+
3
+ * http://github.com/peterc/simredis
4
+
5
+ == STATUS:
6
+
7
+ SimRedis is NOT READY FOR ANY SORT OF SERIOUS USE YET! Currently all of the generic and string methods are working OK, except for anything involving expiration or blocking.
8
+
9
+ == DESCRIPTION:
10
+
11
+ SimRedis is a Redis simulator for the popular redis-rb library. SimRedis lets you use the Redis Ruby client library without a Redis daemon running. Everything is stored in memory only and is very thread unsafe, but persistence and locking comes in time.
12
+
13
+ Why simulate Redis? I love Redis and some of the ideas it brings to the table, and while I can roll out onto servers stocked with working Redis installs OK, sometimes I might want to still use Redis in my app but not require its full power (or I might want to deploy somewhere that doesn't have Redis - like Dreamhost, say). I'd also like to distribute apps I make so that other people can use them without being forced to install a whole new daemon first.
14
+
15
+ == EXAMPLES:
16
+
17
+ require 'redis'
18
+ require 'simredis'
19
+
20
+ r = Redis.new
21
+ r.set("a", "hello world")
22
+ r.get("a") # => "hello world"
23
+ r.keys("*") # => ['a']
24
+ r.flushdb
25
+ r.keys("*") # => []
26
+
27
+ SimRedis comes with tests (of a sort) "baked in." Running the simredis.rb file directly runs the suite.
28
+
29
+ == Note on Patches/Pull Requests
30
+
31
+ * Fork the project.
32
+ * Make your feature addition or bug fix.
33
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
34
+ * Commit, do not mess with Rakefile, version, or history as it's handled by Jeweler (which is awesome, btw).
35
+ * Send me a pull request. I may or may not accept it (sorry, practicality rules.. but message me and we can talk!)
36
+
37
+ == COPYRIGHT AND LICENSE
38
+
39
+ Copyright (c) 2010 Peter Cooper
40
+
41
+ Permission is hereby granted, free of charge, to any person obtaining
42
+ a copy of this software and associated documentation files (the
43
+ "Software"), to deal in the Software without restriction, including
44
+ without limitation the rights to use, copy, modify, merge, publish,
45
+ distribute, sublicense, and/or sell copies of the Software, and to
46
+ permit persons to whom the Software is furnished to do so, subject to
47
+ the following conditions:
48
+
49
+ The above copyright notice and this permission notice shall be
50
+ included in all copies or substantial portions of the Software.
51
+
52
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
53
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
54
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
55
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
56
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
57
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
58
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gemspec|
4
+ gemspec.name = "simredis"
5
+ gemspec.summary = "Redis 'simulator' that allows you to use redis-rb without a Redis daemon running"
6
+ gemspec.description = "Redis 'simulator' that allows you to use redis-rb without a Redis daemon running. Useful in situations where you want to deploy basic Redis-based apps quickly or to people who haven't got the ability to set up the Redis daemon."
7
+ gemspec.email = "simredis@peterc.org"
8
+ gemspec.homepage = "http://github.com/peterc/simredis"
9
+ gemspec.authors = ["Peter Cooper"]
10
+ end
11
+ Jeweler::GemcutterTasks.new
12
+ rescue LoadError
13
+ puts "Jeweler not available. Install it with: gem install jeweler"
14
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.2
data/simredis.gemspec ADDED
@@ -0,0 +1,41 @@
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{simredis}
8
+ s.version = "0.0.2"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Peter Cooper"]
12
+ s.date = %q{2010-05-16}
13
+ s.description = %q{Redis 'simulator' that allows you to use redis-rb without a Redis daemon running. Useful in situations where you want to deploy basic Redis-based apps quickly or to people who haven't got the ability to set up the Redis daemon.}
14
+ s.email = %q{simredis@peterc.org}
15
+ s.extra_rdoc_files = [
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ "README.rdoc",
20
+ "Rakefile",
21
+ "VERSION",
22
+ "simredis.gemspec",
23
+ "simredis.rb"
24
+ ]
25
+ s.homepage = %q{http://github.com/peterc/simredis}
26
+ s.rdoc_options = ["--charset=UTF-8"]
27
+ s.require_paths = ["lib"]
28
+ s.rubygems_version = %q{1.3.5}
29
+ s.summary = %q{Redis 'simulator' that allows you to use redis-rb without a Redis daemon running}
30
+
31
+ if s.respond_to? :specification_version then
32
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
33
+ s.specification_version = 3
34
+
35
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
36
+ else
37
+ end
38
+ else
39
+ end
40
+ end
41
+
data/simredis.rb ADDED
@@ -0,0 +1,597 @@
1
+ require 'rubygems'
2
+ require 'redis'
3
+
4
+ class Redis
5
+ class Emulator
6
+ PREFIX = {
7
+ :error => "-",
8
+ :status => "+",
9
+ :integer => ":",
10
+ :bulk => "$",
11
+ :multi_bulk => "*"
12
+ }
13
+
14
+ STATUS = lambda { |m| [PREFIX[:status], m.to_s] }
15
+ ERROR = lambda { |m| [PREFIX[:error], m.to_s] }
16
+ BULK = lambda { |m|
17
+ if m
18
+ [PREFIX[:bulk], m.to_s.length.to_s, m.to_s]
19
+ else
20
+ [PREFIX[:bulk], -1]
21
+ end
22
+ }
23
+ INTEGER = lambda { |m| [PREFIX[:integer], m.to_i] }
24
+ MULTI_BULK = lambda { |m|
25
+ response = [PREFIX[:multi_bulk], m.length.to_s]
26
+ m.each do |m1|
27
+ if m1
28
+ response += [PREFIX[:bulk], m1.to_s.length.to_s, m1.to_s, "\r\n"]
29
+ else
30
+ response += [PREFIX[:bulk], -1]
31
+ end
32
+ end
33
+ response
34
+ }
35
+
36
+ def initialize
37
+ flushall
38
+ select([0])
39
+ end
40
+
41
+ # ---- CONNECTION HANDLING COMMANDS
42
+
43
+ # http://code.google.com/p/redis/wiki/QuitCommand
44
+ def quit(args, data); end
45
+
46
+ # http://code.google.com/p/redis/wiki/AuthCommand
47
+ def auth(args, data)
48
+ # TODO: Actually do authentication. Ignore for now.
49
+ STATUS[:OK]
50
+ end
51
+
52
+ # ---- GENERIC COMMANDS
53
+
54
+ def exists(args)
55
+ [PREFIX[:integer], @data[args.first] ? "1" : "0"]
56
+ end
57
+
58
+ # http://code.google.com/p/redis/wiki/DelCommand
59
+ def del(args)
60
+ removed = 0
61
+ args.each do |arg|
62
+ removed += 1 if @data[arg]
63
+ @data.delete(arg)
64
+ end
65
+
66
+ INTEGER[removed]
67
+ end
68
+
69
+ def type(args)
70
+ return STATUS[:none] unless @data[args.first]
71
+ return STATUS[:string] if String === @data[args.first]
72
+ return STATUS[:list] if Array === @data[args.first]
73
+ return STATUS[:set] if Set === @data[args.first]
74
+ return STATUS[:zset] if SortedSet === @data[args.first]
75
+ return STATUS[:hash] if Hash === @data[args.first]
76
+ end
77
+
78
+ def keys(args)
79
+ regex = args.first.gsub('?', '.').gsub('*', '.*')
80
+ MULTI_BULK[@data.keys.select { |k| k =~ /#{regex}/ }]
81
+ end
82
+
83
+ def randomkey(args)
84
+ STATUS[@data.keys[rand(@data.size)]]
85
+ end
86
+
87
+ def rename(args)
88
+ from, to = args
89
+ @data[to] = @data[from]
90
+ @data.delete(from)
91
+ STATUS[:OK]
92
+ end
93
+
94
+ def renamenx(args)
95
+ from, to = args
96
+ return INTEGER[0] if @data[to]
97
+ rename(args)
98
+ INTEGER[1]
99
+ end
100
+
101
+ def dbsize(args)
102
+ INTEGER[@data.size]
103
+ end
104
+
105
+ # TODO: Implement!
106
+ def expire(args)
107
+ end
108
+
109
+ # TODO: Implement!
110
+ def ttl(args)
111
+ end
112
+
113
+ def select(args = nil, data = nil)
114
+ @current_db = args.first.to_i
115
+ @data = @datasets[@current_db]
116
+ STATUS[:OK]
117
+ end
118
+
119
+ def move(args)
120
+ key, dbindex = args
121
+ return INTEGER[0] if !@data[key]
122
+ return INTEGER[0] if @datasets[dbindex.to_i][key]
123
+ @datasets[dbindex.to_i][key] = @data[key]
124
+ @data.delete(key)
125
+ INTEGER[1]
126
+ end
127
+
128
+ def flushdb(args = nil, data = nil)
129
+ @data = {}
130
+ STATUS[:OK]
131
+ end
132
+
133
+ def flushall(args = nil, data = nil)
134
+ @datasets = Array.new(32).map { |el| Hash.new }
135
+ @data = @datasets[@current_db || 0]
136
+ STATUS[:OK]
137
+ end
138
+
139
+ # ---- STRING COMMANDS
140
+
141
+ # http://code.google.com/p/redis/wiki/SetCommand
142
+ def set(args)
143
+ @data[args.shift] = args.shift
144
+ STATUS[:OK]
145
+ end
146
+
147
+ # http://code.google.com/p/redis/wiki/GetCommand
148
+ def get(args)
149
+ if @data[args.first]
150
+ BULK[@data[args.first]]
151
+ else
152
+ [PREFIX[:bulk], -1]
153
+ end
154
+ end
155
+
156
+ def getset(args)
157
+ key, value = args
158
+ old_data = @data[args.first]
159
+ p old_data
160
+ set(args)
161
+ if old_data
162
+ BULK[old_data]
163
+ else
164
+ [PREFIX[:bulk], -1]
165
+ end
166
+ end
167
+
168
+ def mget(args)
169
+ MULTI_BULK[args.map { |a| @data[a] }]
170
+ end
171
+
172
+ def setnx(args)
173
+ return INTEGER[0] if @data[args.first]
174
+ set(args)
175
+ INTEGER[1]
176
+ end
177
+
178
+ # TODO: SETEX depends on EXPIRE and TTL
179
+ def setex(args)
180
+ end
181
+
182
+ def mset(args)
183
+ until args.empty?
184
+ key, value = args.shift, args.shift
185
+ set([key, value])
186
+ end
187
+ STATUS[:OK]
188
+ end
189
+
190
+ def msetnx(args)
191
+ to_set = {}
192
+ until args.empty?
193
+ key, value = args.shift, args.shift
194
+ return INTEGER[0] if @data[key]
195
+ to_set[key] = value
196
+ end
197
+
198
+ to_set.each do |key, value|
199
+ set([key, value])
200
+ end
201
+
202
+ INTEGER[1]
203
+ end
204
+
205
+ def incr(args)
206
+ key = args.shift
207
+
208
+ unless @data[key]
209
+ @data[key] = 1
210
+ return INTEGER[1]
211
+ end
212
+
213
+ if @data[key] && (@data[key].to_i.to_s == @data[key].to_s)
214
+ @data[key] = @data[key].to_i + 1
215
+ return INTEGER[@data[key]]
216
+ end
217
+
218
+ ERROR["ERR value is not an integer"]
219
+ end
220
+
221
+ def incrby(args)
222
+ key = args.shift
223
+ by = args.shift.to_i
224
+
225
+ unless @data[key]
226
+ @data[key] = by
227
+ return INTEGER[by]
228
+ end
229
+
230
+ if @data[key] && (@data[key].to_i.to_s == @data[key].to_s)
231
+ @data[key] = @data[key].to_i + by.to_i
232
+ return INTEGER[@data[key]]
233
+ end
234
+
235
+ ERROR["ERR value is not an integer"]
236
+ end
237
+
238
+ def decr(args)
239
+ key = args.shift
240
+
241
+ unless @data[key]
242
+ @data[key] = -1
243
+ return INTEGER[-1]
244
+ end
245
+
246
+ if @data[key] && (@data[key].to_i.to_s == @data[key].to_s)
247
+ @data[key] = @data[key].to_i - 1
248
+ return INTEGER[@data[key]]
249
+ end
250
+
251
+ ERROR["ERR value is not an integer"]
252
+ end
253
+
254
+ def decrby(args)
255
+ key = args.shift
256
+ by = args.shift.to_i
257
+
258
+ unless @data[key]
259
+ @data[key] = -by
260
+ return INTEGER[-by]
261
+ end
262
+
263
+ if @data[key] && (@data[key].to_i.to_s == @data[key].to_s)
264
+ @data[key] = @data[key].to_i + -by.to_i
265
+ return INTEGER[@data[key]]
266
+ end
267
+
268
+ ERROR["ERR value is not an integer"]
269
+ end
270
+
271
+ def append(args)
272
+ key, value = args
273
+
274
+ @data[key] ||= ''
275
+ @data[key] += value
276
+
277
+ INTEGER[@data[key].length]
278
+ end
279
+
280
+ def substr(args)
281
+ key, startp, endp = args
282
+ return BULK[[nil]] unless @data[key]
283
+ return BULK[@data[key].to_s[startp.to_i..endp.to_i]]
284
+ end
285
+
286
+ # ---- LIST COMMANDS
287
+
288
+ def rpush(args)
289
+ key, string = args
290
+ @data[key] ||= []
291
+ return ERROR["ERR Operation against a key holding the wrong kind of value"] unless Array === @data[key]
292
+ @data[key] << string
293
+ INTEGER[@data[key].length]
294
+ end
295
+
296
+ def lpush(args)
297
+ key, string = args
298
+ @data[key] ||= []
299
+ return ERROR["ERR Operation against a key holding the wrong kind of value"] unless Array === @data[key]
300
+ @data[key].unshift(string)
301
+ INTEGER[@data[key].length]
302
+ end
303
+
304
+ def llen(args)
305
+ key = args.shift
306
+ return INTEGER[0] unless @data[key] && Array === @data[key]
307
+ return INTEGER[@data[key].length]
308
+ end
309
+
310
+ def lrange(args)
311
+ key, startp, endp = args
312
+ return MULTI_BULK[[]] unless @data[key] && Array === @data[key]
313
+ return MULTI_BULK[@data[key][startp.to_i..endp.to_i]]
314
+ end
315
+
316
+ def ltrim(args)
317
+ key, startp, endp = args
318
+ if @data[key] && Array === @data[key]
319
+ @data[key] = @data[key][startp.to_i..endp.to_i]
320
+ end
321
+ return STATUS[:OK]
322
+ end
323
+
324
+ def lindex(args)
325
+ key, index = args
326
+ return BULK[[nil]] unless @data[key]
327
+ return BULK[@data[key][index.to_i]]
328
+ end
329
+
330
+ def lset(args)
331
+ key, index, value = args
332
+ return ERROR["ERR no such key"] unless @data[key]
333
+ return ERROR["ERR Operation against a key holding the wrong kind of value"] unless Array === @data[key]
334
+ return ERROR["ERR index out of range"] if index.to_i < 0 || index.to_i > @data[key].length - 1
335
+ @data[key][index.to_i] = value
336
+ STATUS[:OK]
337
+ end
338
+
339
+ def lrem(args)
340
+ key, count, value = args
341
+ return ERROR["ERR no such key"] unless @data[key]
342
+ return ERROR["ERR Operation against a key holding the wrong kind of value"] unless Array === @data[key]
343
+
344
+ old_length = @data[key].size
345
+
346
+ count.to_i.times do |i|
347
+ if posi = @data[key].index(value.to_s)
348
+ @data[key].delete_at(posi)
349
+ end
350
+ end
351
+
352
+ INTEGER[old_length - @data[key].size]
353
+ end
354
+
355
+ def lpop(args)
356
+ key = args.first
357
+ return ERROR["ERR no such key"] unless @data[key]
358
+ return ERROR["ERR Operation against a key holding the wrong kind of value"] unless Array === @data[key]
359
+ BULK[@data[key].shift]
360
+ end
361
+
362
+ def rpop(args)
363
+ key = args.first
364
+ return ERROR["ERR no such key"] unless @data[key]
365
+ return ERROR["ERR Operation against a key holding the wrong kind of value"] unless Array === @data[key]
366
+ BULK[@data[key].pop]
367
+ end
368
+
369
+ # TODO: BLocking POPs.. not sure if it's worth it given this is just an emulator, though.. :-)
370
+ #def blpop(args)
371
+ # timeout = args.pop
372
+ #end
373
+ #
374
+ #def brpop(args)
375
+ # timeout = args.pop
376
+ #end
377
+
378
+ def rpoplpush(args)
379
+ skey, dkey = args
380
+ return BULK[nil] unless @data[skey] && Array === @data[skey]
381
+ @data[dkey] ||= []
382
+ data = @data[skey].pop
383
+ @data[dkey].unshift(data)
384
+ BULK[data]
385
+ end
386
+ end
387
+
388
+
389
+
390
+
391
+ # redis-rb uses TCPSocket to communicate with the Redis daemon
392
+ # If we put another TCPSocket /closer/ to Redis, this one will take
393
+ # precedence, but not trample over other TCPSocket classes outside of redis-rb
394
+ class TCPSocket
395
+ def initialize(host, port)
396
+ @emulator = Emulator.new
397
+ @output_queue = []
398
+ end
399
+
400
+ def write(data)
401
+ puts "SEND: #{data.inspect}" if TESTING
402
+ cmd, *data = data.split("\r\n")
403
+ if cmd =~ /^\*/
404
+ data.delete_if { |d| d =~ /^\$/ }
405
+ cmd = data.shift
406
+ args = data
407
+ #p cmd
408
+ #p args
409
+ else
410
+ cmd, *args = cmd.split(' ')
411
+ end
412
+ @output_queue += @emulator.send(cmd.downcase.to_sym, args)
413
+ puts "RECV: #{@output_queue.inspect}" if TESTING
414
+ end
415
+
416
+ def read(b); @output_queue.shift; end
417
+ def gets; @output_queue.shift; end
418
+ def setsockopt(*ignored); end
419
+ end
420
+ end
421
+
422
+
423
+ if __FILE__ == $0
424
+ # Poor man's testing
425
+ def assert(c)
426
+ #$an ||= 0
427
+ #puts $an += 1
428
+ abort unless c
429
+ puts
430
+ end
431
+
432
+ TESTING = true
433
+ r = Redis.new(:db => 10)
434
+
435
+ # Clear everything
436
+ assert r.flushall
437
+
438
+ # Ensure the "a" key does not exist, set it, re-set it, then check its value, existance, and type
439
+ assert !r.exists("a")
440
+ assert r.set("a", 10)
441
+ assert r.set("a", 50)
442
+ assert r.get("a") == "50"
443
+ assert r.exists("a")
444
+ assert r.type("a") == "string"
445
+
446
+ # Pick a random key - there's only one so far, of course
447
+ assert r.randomkey == "a"
448
+
449
+ # Remove "a" and then ensure it has no type, does not exist, and is not in the keys list
450
+ assert r.del("a")
451
+ assert r.type("a") == "none"
452
+ assert !r.exists("a")
453
+ assert r.keys("*") == []
454
+
455
+ # Set keys "a" and "b" and do key lists to prove they exist
456
+ assert r.set("a", "this is a test")
457
+ assert r.keys("*") == %w{a}
458
+ assert r.set("b", "this is a test too")
459
+ assert r.keys("*") == %w{a b}
460
+
461
+ # Flush this specific database and ensure it worked
462
+ assert r.flushdb
463
+ assert r.keys("*") == []
464
+ assert r.dbsize == 0
465
+
466
+ # Test renaming features
467
+ assert r.set("a", 10)
468
+ assert r.rename("a", "b")
469
+ assert r.get("b") == "10"
470
+ assert !r.get("a")
471
+ assert r.set("c", "hello world")
472
+ assert !r.renamenx("c", "b")
473
+ assert r.renamenx("c", "a")
474
+ assert r.type("c") == "none"
475
+
476
+ # Test moving - in a basic way since redis-rb won't let us select back and forth easily
477
+ assert r.set("a", 10)
478
+ assert r.move("a", 2)
479
+ assert !r.exists("a")
480
+ assert r.flushall
481
+
482
+ # Test getset to both non-existing and pre-existing keys
483
+ assert r.getset("b", 10) == nil
484
+ assert r.get("b") == "10"
485
+ assert r.set("a", "x")
486
+ assert r.getset("a", "y") == "x"
487
+ assert r.flushdb
488
+
489
+ # Test mget (multiget)
490
+ assert r.set("a", 100)
491
+ assert r.set("b", 200)
492
+ assert r.set("c", 300)
493
+ assert r.mget("a", "b", "c") == %w{100 200 300}
494
+ assert r.del("b")
495
+ p r.mget("a", "b", "c")
496
+ assert r.mget("a", "b", "c") == ["100", nil, "300"]
497
+
498
+ # Test setnx (set but not if key already exists)
499
+ assert r.setnx("abc", 10)
500
+ assert r.get("abc") == "10"
501
+ assert !r.setnx("abc", 20)
502
+ assert r.get("abc") == "10"
503
+ assert r.flushdb
504
+
505
+ # Test mset (multi set)
506
+ assert r.mset("a", 100, "b", 200, "c", 300)
507
+ assert r.mget("a", "b", "c") == %w{100 200 300}
508
+ assert r.flushdb
509
+
510
+ # Test msetnx (multi set but not if present)
511
+ assert r.msetnx("a", 100, "b", 200, "c", 300) == 1
512
+ assert r.mget("a", "b", "c") == %w{100 200 300}
513
+ assert r.msetnx("a", 1, "b", 2, "c", 3) == 0
514
+ assert r.mget("a", "b", "c") == %w{100 200 300}
515
+ assert r.flushdb
516
+
517
+ # Test incr, decr, incrby and decrby
518
+ assert r.incr("a") == 1
519
+ assert r.incr("a") == 2
520
+ assert r.set("b", "xyz")
521
+ assert((r.incr("b") rescue :freakout) == :freakout)
522
+ assert r.incrby("a", 2) == 4
523
+ assert r.decr("a") == 3
524
+ assert r.decrby("a", 4) == -1
525
+ assert r.decrby("xxx", 10) == -10
526
+ assert r.incrby("xxx2", 10) == 10
527
+ assert r.flushdb
528
+
529
+ # Test append (test taken right from the Redis wiki.. :-)
530
+ assert !r.exists("mykey")
531
+ assert r.append("mykey", "Hello ") == 6
532
+ assert r.append("mykey", "World") == 11
533
+ assert r.get("mykey") == "Hello World"
534
+
535
+ # Test substr
536
+ assert r.set("s", "This is a string")
537
+ assert r.substr("s", 0, 3) == "This"
538
+ assert r.substr("s", -3, -1) == "ing"
539
+ assert r.substr("s", 0, -1) == "This is a string"
540
+ assert r.substr("s", 9, 100000) == " string"
541
+
542
+ # Test rpush, lpush, llen, ltrim, lrange, lindex, and lset
543
+ assert r.flushdb
544
+ assert r.lpush("a", "hello2") == 1
545
+ assert r.lpush("a", "hello1") == 2
546
+ assert r.llen("a") == 2
547
+ assert r.rpush("a", "hello3") == 3
548
+ assert r.llen("a") == 3
549
+ assert r.lrange("a", 0, -1) == %w{hello1 hello2 hello3}
550
+ assert r.lrange("a", 1, 2) == %w{hello2 hello3}
551
+ assert r.ltrim("a", 1, 2)
552
+ assert r.lrange("a", 0, -1) == %w{hello2 hello3}
553
+ assert r.lindex("a", 0) == "hello2"
554
+ assert r.lindex("a", 1) == "hello3"
555
+ assert r.lindex("a", -2) == "hello2"
556
+ assert r.lset("a", 1, "hello4")
557
+ assert r.lindex("a", 1) == "hello4"
558
+ assert((r.lset("a", 100, "hello4") rescue :freakout) == :freakout)
559
+ assert((r.lset("c", 100, "hello4") rescue :freakout) == :freakout)
560
+ assert r.flushdb
561
+
562
+ # Test lrem, lpop, rpop, blpop, brpop, and rpoplpush
563
+ assert r.rpush("a", "a")
564
+ assert r.rpush("a", "b")
565
+ assert r.rpush("a", "a")
566
+ assert r.rpush("a", "c")
567
+ assert r.rpush("a", "a")
568
+ assert r.lrem("a", 1, "a") == 1
569
+ assert r.lrange("a", 0, -1) == %w{b a c a}
570
+ assert r.lrem("a", 3, "a") == 2
571
+ assert r.lrange("a", 0, -1) == %w{b c}
572
+ assert r.rpush("a", "a")
573
+ # Do a little side test to make sure numbers don't escape the wrath of lrem
574
+ assert r.rpush("a", 20)
575
+ assert r.rpush("a", 40)
576
+ assert r.lrange("a", 0, -1) == %w{b c a 20 40}
577
+ assert r.lrem("a", 1, 20)
578
+ assert r.lrange("a", 0, -1) == %w{b c a 40}
579
+ # Resume with popping tests!
580
+ assert r.lpop("a") == "b"
581
+ assert r.lrange("a", 0, -1) == %w{c a 40}
582
+ assert r.rpop("a") == "40"
583
+ assert r.lrange("a", 0, -1) == %w{c a}
584
+ assert r.flushdb
585
+
586
+ # Test rpoplpush
587
+ assert r.rpush("a", "a")
588
+ assert r.rpush("a", "b")
589
+ assert r.rpush("a", "c")
590
+ assert r.rpoplpush("a", "b") == "c"
591
+ assert r.lrange("a", 0, -1) == %w{a b}
592
+ p r.lrange("b", 0, -1)
593
+ assert r.lrange("b", 0, -1) == %w{c}
594
+
595
+
596
+ puts "BINGO!"
597
+ end
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simredis
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Peter Cooper
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-05-16 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Redis 'simulator' that allows you to use redis-rb without a Redis daemon running. Useful in situations where you want to deploy basic Redis-based apps quickly or to people who haven't got the ability to set up the Redis daemon.
17
+ email: simredis@peterc.org
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ files:
25
+ - README.rdoc
26
+ - Rakefile
27
+ - VERSION
28
+ - simredis.gemspec
29
+ - simredis.rb
30
+ has_rdoc: true
31
+ homepage: http://github.com/peterc/simredis
32
+ licenses: []
33
+
34
+ post_install_message:
35
+ rdoc_options:
36
+ - --charset=UTF-8
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ requirements: []
52
+
53
+ rubyforge_project:
54
+ rubygems_version: 1.3.5
55
+ signing_key:
56
+ specification_version: 3
57
+ summary: Redis 'simulator' that allows you to use redis-rb without a Redis daemon running
58
+ test_files: []
59
+