yam-redis-with-retries 2.2.2.1

Sign up to get free protection for your applications and to get access to all the features.
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