yam-redis-with-retries 2.2.2.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.
Files changed (81) hide show
  1. data/CHANGELOG.md +53 -0
  2. data/LICENSE +20 -0
  3. data/README.md +208 -0
  4. data/Rakefile +277 -0
  5. data/benchmarking/logging.rb +62 -0
  6. data/benchmarking/pipeline.rb +51 -0
  7. data/benchmarking/speed.rb +21 -0
  8. data/benchmarking/suite.rb +24 -0
  9. data/benchmarking/thread_safety.rb +38 -0
  10. data/benchmarking/worker.rb +71 -0
  11. data/examples/basic.rb +15 -0
  12. data/examples/dist_redis.rb +43 -0
  13. data/examples/incr-decr.rb +17 -0
  14. data/examples/list.rb +26 -0
  15. data/examples/pubsub.rb +31 -0
  16. data/examples/sets.rb +36 -0
  17. data/examples/unicorn/config.ru +3 -0
  18. data/examples/unicorn/unicorn.rb +20 -0
  19. data/lib/redis.rb +1166 -0
  20. data/lib/redis/client.rb +265 -0
  21. data/lib/redis/compat.rb +21 -0
  22. data/lib/redis/connection.rb +9 -0
  23. data/lib/redis/connection/command_helper.rb +45 -0
  24. data/lib/redis/connection/hiredis.rb +49 -0
  25. data/lib/redis/connection/registry.rb +12 -0
  26. data/lib/redis/connection/ruby.rb +135 -0
  27. data/lib/redis/connection/synchrony.rb +129 -0
  28. data/lib/redis/distributed.rb +694 -0
  29. data/lib/redis/hash_ring.rb +131 -0
  30. data/lib/redis/pipeline.rb +34 -0
  31. data/lib/redis/retry.rb +128 -0
  32. data/lib/redis/subscribe.rb +94 -0
  33. data/lib/redis/version.rb +3 -0
  34. data/test/commands_on_hashes_test.rb +20 -0
  35. data/test/commands_on_lists_test.rb +60 -0
  36. data/test/commands_on_sets_test.rb +78 -0
  37. data/test/commands_on_sorted_sets_test.rb +109 -0
  38. data/test/commands_on_strings_test.rb +80 -0
  39. data/test/commands_on_value_types_test.rb +88 -0
  40. data/test/connection_handling_test.rb +88 -0
  41. data/test/distributed_blocking_commands_test.rb +53 -0
  42. data/test/distributed_commands_on_hashes_test.rb +12 -0
  43. data/test/distributed_commands_on_lists_test.rb +24 -0
  44. data/test/distributed_commands_on_sets_test.rb +85 -0
  45. data/test/distributed_commands_on_strings_test.rb +50 -0
  46. data/test/distributed_commands_on_value_types_test.rb +73 -0
  47. data/test/distributed_commands_requiring_clustering_test.rb +148 -0
  48. data/test/distributed_connection_handling_test.rb +25 -0
  49. data/test/distributed_internals_test.rb +27 -0
  50. data/test/distributed_key_tags_test.rb +53 -0
  51. data/test/distributed_persistence_control_commands_test.rb +24 -0
  52. data/test/distributed_publish_subscribe_test.rb +101 -0
  53. data/test/distributed_remote_server_control_commands_test.rb +43 -0
  54. data/test/distributed_sorting_test.rb +21 -0
  55. data/test/distributed_test.rb +59 -0
  56. data/test/distributed_transactions_test.rb +34 -0
  57. data/test/encoding_test.rb +16 -0
  58. data/test/error_replies_test.rb +53 -0
  59. data/test/helper.rb +145 -0
  60. data/test/internals_test.rb +163 -0
  61. data/test/lint/hashes.rb +126 -0
  62. data/test/lint/internals.rb +37 -0
  63. data/test/lint/lists.rb +93 -0
  64. data/test/lint/sets.rb +66 -0
  65. data/test/lint/sorted_sets.rb +167 -0
  66. data/test/lint/strings.rb +137 -0
  67. data/test/lint/value_types.rb +84 -0
  68. data/test/persistence_control_commands_test.rb +22 -0
  69. data/test/pipelining_commands_test.rb +123 -0
  70. data/test/publish_subscribe_test.rb +158 -0
  71. data/test/redis_mock.rb +80 -0
  72. data/test/remote_server_control_commands_test.rb +82 -0
  73. data/test/retry_test.rb +225 -0
  74. data/test/sorting_test.rb +44 -0
  75. data/test/synchrony_driver.rb +57 -0
  76. data/test/test.conf +8 -0
  77. data/test/thread_safety_test.rb +30 -0
  78. data/test/transactions_test.rb +100 -0
  79. data/test/unknown_commands_test.rb +14 -0
  80. data/test/url_param_test.rb +60 -0
  81. metadata +215 -0
data/CHANGELOG.md ADDED
@@ -0,0 +1,53 @@
1
+ # 2.2.3 (unreleased)
2
+
3
+ # 2.2.2
4
+
5
+ * Added method `Redis::Distributed#hsetnx`.
6
+
7
+ # 2.2.1
8
+
9
+ * Internal API: Client#call and family are now called with a single array
10
+ argument, since splatting a large number of arguments (100K+) results in a
11
+ stack overflow on 1.9.2.
12
+
13
+ * The `INFO` command can optionally take a subcommand. When the subcommand is
14
+ `COMMANDSTATS`, the client will properly format the returned statistics per
15
+ command. Subcommands for `INFO` are available since Redis v2.3.0 (unstable).
16
+
17
+ * Change `IO#syswrite` back to the buffered `IO#write` since some Rubies do
18
+ short writes for large (1MB+) buffers and some don't (see issue #108).
19
+
20
+ # 2.2.0
21
+
22
+ * Added method `Redis#without_reconnect` that ensures the client will not try
23
+ to reconnect when running the code inside the specified block.
24
+
25
+ * Thread-safe by default. Thread safety can be explicitly disabled by passing
26
+ `:thread_safe => false` as argument.
27
+
28
+ * Commands called inside a MULTI/EXEC no longer raise error replies, since a
29
+ successful EXEC means the commands inside the block were executed.
30
+
31
+ * MULTI/EXEC blocks are pipelined.
32
+
33
+ * Don't disconnect on error replies.
34
+
35
+ * Use `IO#syswrite` instead of `IO#write` because write buffering is not
36
+ necessary.
37
+
38
+ * Connect to a unix socket by passing the `:path` option as argument.
39
+
40
+ * The timeout value is coerced into a float, allowing sub-second timeouts.
41
+
42
+ * Accept both `:with_scores` _and_ `:withscores` as argument to sorted set
43
+ commands.
44
+
45
+ * Use [hiredis](https://github.com/pietern/hiredis-rb) (v0.3 or higher) by
46
+ requiring "redis/connection/hiredis".
47
+
48
+ * Use [em-synchrony](https://github.com/igrigorik/em-synchrony) by requiring
49
+ "redis/connection/synchrony".
50
+
51
+ # 2.1.1
52
+
53
+ See commit log.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Ezra Zygmuntowicz
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.md ADDED
@@ -0,0 +1,208 @@
1
+ # redis-rb
2
+
3
+ A Ruby client library for the [Redis](http://redis.io) key-value store.
4
+
5
+ ## A note about versions
6
+
7
+ Versions *1.0.x* target all versions of Redis. You have to use this one if you are using Redis < 1.2.
8
+
9
+ Version *2.0* is a big refactoring of the previous version and makes little effort to be
10
+ backwards-compatible when it shouldn't. It does not support Redis' original protocol, favoring the
11
+ new, binary-safe one. You should be using this version if you're running Redis 1.2+.
12
+
13
+ ## Information about Redis
14
+
15
+ Redis is a key-value store with some interesting features:
16
+
17
+ 1. It's fast.
18
+ 2. Keys are strings but values are typed. Currently Redis supports strings, lists, sets, sorted sets and hashes. [Atomic operations](http://redis.io/commands) can be done on all of these types.
19
+
20
+ See [the Redis homepage](http://redis.io) for more information.
21
+
22
+ ## Getting started
23
+
24
+ You can connect to Redis by instantiating the `Redis` class:
25
+
26
+ require "redis"
27
+
28
+ redis = Redis.new
29
+
30
+ This assumes Redis was started with default values listening on `localhost`, port 6379. If you need to connect to a remote server or a different port, try:
31
+
32
+ redis = Redis.new(:host => "10.0.1.1", :port => 6380)
33
+
34
+ To connect to Redis listening on a unix socket, try:
35
+
36
+ redis = Redis.new(:path => "/tmp/redis.sock")
37
+
38
+ Once connected, you can start running commands against Redis:
39
+
40
+ >> redis.set "foo", "bar"
41
+ => "OK"
42
+
43
+ >> redis.get "foo"
44
+ => "bar"
45
+
46
+ >> redis.sadd "users", "albert"
47
+ => true
48
+
49
+ >> redis.sadd "users", "bernard"
50
+ => true
51
+
52
+ >> redis.sadd "users", "charles"
53
+ => true
54
+
55
+ How many users?
56
+
57
+ >> redis.scard "users"
58
+ => 3
59
+
60
+ Is `albert` a user?
61
+
62
+ >> redis.sismember "users", "albert"
63
+ => true
64
+
65
+ Is `isabel` a user?
66
+
67
+ >> redis.sismember "users", "isabel"
68
+ => false
69
+
70
+ Handle groups:
71
+
72
+ >> redis.sadd "admins", "albert"
73
+ => true
74
+
75
+ >> redis.sadd "admins", "isabel"
76
+ => true
77
+
78
+ Users who are also admins:
79
+
80
+ >> redis.sinter "users", "admins"
81
+ => ["albert"]
82
+
83
+ Users who are not admins:
84
+
85
+ >> redis.sdiff "users", "admins"
86
+ => ["bernard", "charles"]
87
+
88
+ Admins who are not users:
89
+
90
+ >> redis.sdiff "admins", "users"
91
+ => ["isabel"]
92
+
93
+ All users and admins:
94
+
95
+ >> redis.sunion "admins", "users"
96
+ => ["albert", "bernard", "charles", "isabel"]
97
+
98
+
99
+ ## Storing objects
100
+
101
+ Redis only stores strings as values. If you want to store an object inside a key, you can use a serialization/deseralization mechanism like JSON:
102
+
103
+ >> redis.set "foo", [1, 2, 3].to_json
104
+ => OK
105
+
106
+ >> JSON.parse(redis.get("foo"))
107
+ => [1, 2, 3]
108
+
109
+ ## Executing multiple commands atomically
110
+
111
+ You can use `MULTI/EXEC` to run arbitrary commands in an atomic fashion:
112
+
113
+ redis.multi do
114
+ redis.set "foo", "bar"
115
+ redis.incr "baz"
116
+ end
117
+
118
+ ## Multithreaded Operation
119
+
120
+ Starting with redis-rb 2.2.0, the client is thread-safe by default. To use
121
+ earlier versions safely in a multithreaded environment, be sure to initialize
122
+ the client with `:thread_safe => true`. Thread-safety can be explicitly
123
+ disabled for versions 2.2 and up by initializing the client with `:thread_safe
124
+ => false`.
125
+
126
+ See the tests and benchmarks for examples.
127
+
128
+ ## Alternate drivers
129
+
130
+ Non-default connection drivers are only used when they are explicitly required.
131
+ By default, redis-rb uses Ruby's socket library to talk with Redis.
132
+
133
+ ### hiredis
134
+
135
+ Using redis-rb with hiredis-rb (v0.3 or higher) as backend is done by requiring
136
+ `redis/connection/hiredis` before requiring `redis`. This will make redis-rb
137
+ pick up hiredis as default driver automatically. This driver optimizes for
138
+ speed, at the cost of portability. Since hiredis is a C extension, JRuby is not
139
+ supported (by default). Use hiredis when you have large array replies (think
140
+ `LRANGE`, `SMEMBERS`, `ZRANGE`, etc.) and/or large pipelines of commands.
141
+
142
+ Using redis-rb with hiredis from a Gemfile:
143
+
144
+ gem "hiredis", "~> 0.3.1"
145
+ gem "redis", "~> 2.2.0", :require => ["redis/connection/hiredis", "redis"]
146
+
147
+ ### synchrony
148
+
149
+ This driver adds support for
150
+ [em-synchrony](https://github.com/igrigorik/em-synchrony). Using the synchrony
151
+ backend from redis-rb is done by requiring `redis/connection/synchrony` before
152
+ requiring `redis`. This driver makes redis-rb work with EventMachine's
153
+ asynchronous I/O, while not changing the exposed API. The hiredis gem needs to
154
+ be available as well, because the synchrony driver uses hiredis for parsing the
155
+ Redis protocol.
156
+
157
+ Using redis-rb with synchrony from a Gemfile:
158
+
159
+ gem "hiredis", "~> 0.3.1"
160
+ gem "em-synchrony"
161
+ gem "redis", "~> 2.2.0", :require => ["redis/connection/synchrony", "redis"]
162
+
163
+ ## Testing
164
+
165
+ This library (v2.2) is tested against the following interpreters:
166
+
167
+ * MRI 1.8.7 (drivers: Ruby, hiredis)
168
+ * MRI 1.9.2 (drivers: Ruby, hiredis, em-synchrony)
169
+ * JRuby 1.6 (drivers: Ruby)
170
+ * Rubinius 1.2 (drivers: Ruby, hiredis)
171
+
172
+ ## Known issues
173
+
174
+ * Ruby 1.9 doesn't raise on socket timeouts in `IO#read` but rather retries the
175
+ read operation. This means socket timeouts don't work on 1.9 when using the
176
+ pure Ruby I/O code. Use hiredis when you want use socket timeouts on 1.9.
177
+
178
+ * Ruby 1.8 *does* raise on socket timeouts in `IO#read`, but prints a warning
179
+ that using `IO#read` for non blocking reads is obsolete. This is wrong, since
180
+ the read is in fact blocking, but `EAGAIN` (which is returned on socket
181
+ timeouts) is interpreted as if the read was non blocking. Use hiredis to
182
+ prevent seeing this warning.
183
+
184
+ ## More info
185
+
186
+ Check the [Redis Command Reference](http://redis.io/commands) or check the tests to find out how to use this client.
187
+
188
+ ## Contributors
189
+
190
+ (ordered chronologically with more than 5 commits, see `git shortlog -sn` for
191
+ all contributors)
192
+
193
+ * Ezra Zygmuntowicz
194
+ * Taylor Weibley
195
+ * Matthew Clark
196
+ * Brian McKinney
197
+ * Luca Guidi
198
+ * Salvatore Sanfillipo
199
+ * Chris Wanstrath
200
+ * Damian Janowski
201
+ * Michel Martens
202
+ * Nick Quaranto
203
+ * Pieter Noordhuis
204
+ * Ilya Grigorik
205
+
206
+ ## Contributing
207
+
208
+ [Fork the project](http://github.com/ezmobius/redis-rb) and send pull requests. You can also ask for help at `#redis-rb` on Freenode.
data/Rakefile ADDED
@@ -0,0 +1,277 @@
1
+ require 'rubygems'
2
+ require 'rubygems/package_task'
3
+ require 'rake/testtask'
4
+
5
+ $:.unshift File.join(File.dirname(__FILE__), 'lib')
6
+ require 'redis/version'
7
+
8
+ REDIS_DIR = File.expand_path(File.join("..", "test"), __FILE__)
9
+ REDIS_CNF = File.join(REDIS_DIR, "test.conf")
10
+ REDIS_PID = File.join(REDIS_DIR, "db", "redis.pid")
11
+
12
+ task :default => :run
13
+
14
+ desc "Run tests and manage server start/stop"
15
+ task :run => [:start, :test, :stop]
16
+
17
+ desc "Start the Redis server"
18
+ task :start do
19
+ redis_running = \
20
+ begin
21
+ File.exists?(REDIS_PID) && Process.kill(0, File.read(REDIS_PID).to_i)
22
+ rescue Errno::ESRCH
23
+ FileUtils.rm REDIS_PID
24
+ false
25
+ end
26
+
27
+ system "redis-server #{REDIS_CNF}" unless redis_running
28
+ end
29
+
30
+ desc "Stop the Redis server"
31
+ task :stop do
32
+ if File.exists?(REDIS_PID)
33
+ Process.kill "INT", File.read(REDIS_PID).to_i
34
+ FileUtils.rm REDIS_PID
35
+ end
36
+ end
37
+
38
+ def isolated(&block)
39
+ pid = fork { yield }
40
+ Process.wait(pid)
41
+ end
42
+
43
+ desc "Run the test suite"
44
+ task :test => ["test:ruby", "test:hiredis", "test:synchrony"]
45
+
46
+ namespace :test do
47
+
48
+ desc "Run the retry tests"
49
+ task :retry do
50
+ require "cutest"
51
+ isolated do
52
+ Cutest.run(Dir["./test/**/retry_test.rb"])
53
+ end
54
+ end
55
+
56
+ desc "Run tests against the Ruby driver"
57
+ task :ruby do
58
+ require "cutest"
59
+
60
+ isolated do
61
+ Cutest.run(Dir["./test/**/*_test.rb"])
62
+ end
63
+ end
64
+
65
+ desc "Run tests against the hiredis driver"
66
+ task :hiredis do
67
+ require "cutest"
68
+
69
+ isolated do
70
+ begin
71
+ require "redis/connection/hiredis"
72
+
73
+ puts
74
+ puts "Running tests against hiredis v#{Hiredis::VERSION}"
75
+
76
+ Cutest.run(Dir["./test/**/*_test.rb"])
77
+ rescue LoadError
78
+ puts "Skipping tests against hiredis"
79
+ end
80
+ end
81
+ end
82
+
83
+ desc "Run tests against the em-synchrony driver"
84
+ task :synchrony do
85
+ require "cutest"
86
+
87
+ # Synchrony needs 1.9
88
+ next if RUBY_VERSION < "1.9"
89
+
90
+ isolated do
91
+ begin
92
+ require "redis/connection/synchrony"
93
+
94
+ puts
95
+ puts "Running tests against em-synchrony"
96
+
97
+ threaded_tests = ['./test/thread_safety_test.rb']
98
+ Cutest.run(Dir['./test/**/*_test.rb'] - threaded_tests)
99
+ rescue LoadError
100
+ puts "Skipping tests against em-synchrony"
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ task :doc => ["doc:generate", "doc:prepare"]
107
+
108
+ namespace :doc do
109
+ task :generate do
110
+ require "shellwords"
111
+
112
+ `rm -rf doc`
113
+
114
+ current_branch = `git branch`[/^\* (.*)$/, 1]
115
+
116
+ begin
117
+ tags = `git tag -l`.split("\n").sort.reverse
118
+
119
+ tags.each do |tag|
120
+ `git checkout -q #{tag} 2>/dev/null`
121
+
122
+ unless $?.success?
123
+ $stderr.puts "Need a clean working copy. Please git-stash away."
124
+ exit 1
125
+ end
126
+
127
+ puts tag
128
+
129
+ `mkdir -p doc/#{tag}`
130
+
131
+ files = `git ls-tree -r HEAD lib`.split("\n").map do |line|
132
+ line[/\t(.*)$/, 1]
133
+ end
134
+
135
+ opts = [
136
+ "--title", "A Ruby client for Redis",
137
+ "--output", "doc/#{tag}",
138
+ "--no-cache",
139
+ "--no-save",
140
+ "-q",
141
+ *files
142
+ ]
143
+
144
+ `yardoc #{Shellwords.shelljoin opts}`
145
+ end
146
+ ensure
147
+ `git checkout -q #{current_branch}`
148
+ end
149
+ end
150
+
151
+ task :prepare do
152
+ versions = `git tag -l`.split("\n").grep(/^v/).sort
153
+ latest_version = versions.last
154
+
155
+ File.open("doc/.htaccess", "w") do |file|
156
+ file.puts "RedirectMatch 302 ^/?$ /#{latest_version}"
157
+ end
158
+
159
+ File.open("doc/robots.txt", "w") do |file|
160
+ file.puts "User-Agent: *"
161
+
162
+ (versions - [latest_version]).each do |version|
163
+ file.puts "Disallow: /#{version}"
164
+ end
165
+ end
166
+
167
+ google_analytics = <<-EOS
168
+ <script type="text/javascript">
169
+
170
+ var _gaq = _gaq || [];
171
+ _gaq.push(['_setAccount', 'UA-11356145-2']);
172
+ _gaq.push(['_trackPageview']);
173
+
174
+ (function() {
175
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
176
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
177
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
178
+ })();
179
+
180
+ </script>
181
+ EOS
182
+
183
+ Dir["doc/**/*.html"].each do |path|
184
+ lines = IO.readlines(path)
185
+
186
+ File.open(path, "w") do |file|
187
+ lines.each do |line|
188
+ if line.include?("</head>")
189
+ file.write(google_analytics)
190
+ end
191
+
192
+ file.write(line)
193
+ end
194
+ end
195
+ end
196
+ end
197
+
198
+ task :deploy do
199
+ system "rsync --del -avz doc/ redis-rb.keyvalue.org:deploys/redis-rb.keyvalue.org/"
200
+ end
201
+ end
202
+
203
+ namespace :commands do
204
+ def redis_commands
205
+ $redis_commands ||= doc.keys.map do |key|
206
+ key.split(" ").first.downcase
207
+ end.uniq
208
+ end
209
+
210
+ def doc
211
+ $doc ||= begin
212
+ require "open-uri"
213
+ require "json"
214
+
215
+ JSON.parse(open("https://github.com/antirez/redis-doc/raw/master/commands.json").read)
216
+ end
217
+ end
218
+
219
+ def document(file)
220
+ source = File.read(file)
221
+
222
+ doc.each do |name, command|
223
+ source.sub!(/(?:^ *# .*\n)*(^ *#\n(^ *# .+?\n)*)*^( *)def #{name.downcase}(\(|$)/) do
224
+ extra_comments, indent, extra_args = $1, $3, $4
225
+ comment = "#{indent}# #{command["summary"].strip}."
226
+
227
+ IO.popen("par p#{2 + indent.size} 80", "r+") do |io|
228
+ io.puts comment
229
+ io.close_write
230
+ comment = io.read
231
+ end
232
+
233
+ "#{comment}#{extra_comments}#{indent}def #{name.downcase}#{extra_args}"
234
+ end
235
+ end
236
+
237
+ File.open(file, "w") { |f| f.write(source) }
238
+ end
239
+
240
+ task :doc do
241
+ document "lib/redis.rb"
242
+ document "lib/redis/distributed.rb"
243
+ end
244
+
245
+ task :verify do
246
+ require "redis"
247
+ require "stringio"
248
+
249
+ require "./test/helper"
250
+
251
+ OPTIONS[:logger] = Logger.new("./tmp/log")
252
+
253
+ Rake::Task["test:ruby"].invoke
254
+
255
+ redis = Redis.new
256
+
257
+ report = ["Command", "\033[0mDefined?\033[0m", "\033[0mTested?\033[0m"]
258
+
259
+ yes, no = "\033[1;32mYes\033[0m", "\033[1;31mNo\033[0m"
260
+
261
+ log = File.read("./tmp/log")
262
+
263
+ redis_commands.sort.each do |name, _|
264
+ defined, tested = redis.respond_to?(name), log[">> #{name.upcase}"]
265
+
266
+ next if defined && tested
267
+
268
+ report << name
269
+ report << (defined ? yes : no)
270
+ report << (tested ? yes : no)
271
+ end
272
+
273
+ IO.popen("rs 0 3", "w") do |io|
274
+ io.puts report.join("\n")
275
+ end
276
+ end
277
+ end