redispot 0.1.1 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 12690e01651c34511666f3c387adb439ab9a94b4
4
- data.tar.gz: 95aefdff7ef71d1a1aa328551682713c39866f89
3
+ metadata.gz: 2ab5ec4bc687538bb74c69596ff14e72f99c6332
4
+ data.tar.gz: 67b3382aa1d1f44b5c124a3809ea81dcbea49e96
5
5
  SHA512:
6
- metadata.gz: bc071d7b16ee721be78b43524041abeb1b3a39e8a5869d0d36abfc3553bcba00a99f8ec164b457a4fbda11fe3fe6e5caa45434590c3ade05847adf61605f931a
7
- data.tar.gz: 6a3c599ef734e319b635b5d1dd772c5e1b2e3bc486bf7f825036d8d7f820169ec3cb8d49e8aa711888fb574738e03ba9c570e04d516d3440e806237c4e8b67a0
6
+ metadata.gz: 605045c45c3c2478589896f31c169e03c6c96261e12cacc34fa4d160a043622b4f7881ebf0de2b544c86bf87e5d77860fd7f943a72f1cb67e503193be22035e7
7
+ data.tar.gz: 2c4be779c3fbe2916c0098963ce6cb49ac63323a4a056d030c706f922cab0b180bfbbef56f2c2c79b1d5b47b04861cd36498f2db128e9926eb24b62ce0e4e654
data/.envrc ADDED
@@ -0,0 +1,4 @@
1
+ layout ruby
2
+ export CODECLIMATE_REPO_TOKEN="2193dedb73f3070a2fd119a2c577d97ad00de8becc5147c188f6f4428fe2e94b"
3
+
4
+ # vim: ft=sh
data/.gitignore CHANGED
@@ -1,6 +1,8 @@
1
1
  /.bundle/
2
+ /.direnv/
2
3
  /.yardoc/
3
4
  /Gemfile.lock
5
+ /coverage/
4
6
  /doc/
5
7
  /pkg/
6
8
  /vendor/
data/.travis.yml CHANGED
@@ -3,3 +3,6 @@ rvm:
3
3
  - 2.1.6
4
4
  - 2.2.2
5
5
  before_install: gem install bundler -v 1.10.5
6
+ addons:
7
+ code_climate:
8
+ repo_token: 2193dedb73f3070a2fd119a2c577d97ad00de8becc5147c188f6f4428fe2e94b
data/README.md CHANGED
@@ -2,9 +2,11 @@
2
2
  [![Gem Version](https://badge.fury.io/rb/redispot.svg)](http://badge.fury.io/rb/redispot)
3
3
  [![Build Status](https://travis-ci.org/hatyuki/redispot-rb.svg?branch=master)](https://travis-ci.org/hatyuki/redispot-rb)
4
4
  [![Code Climate](https://codeclimate.com/github/hatyuki/redispot-rb/badges/gpa.svg)](https://codeclimate.com/github/hatyuki/redispot-rb)
5
+ [![Test Coverage](https://codeclimate.com/github/hatyuki/redispot-rb/badges/coverage.svg)](https://codeclimate.com/github/hatyuki/redispot-rb/coverage)
5
6
 
6
- Launching the redis-server instance which is available only within a block.
7
+ Launching the redis-server instance which is available only within a scope.
7
8
  It is useful when you want to test your code.
9
+
8
10
  It is a Ruby clone of [Test::RedisServer](https://github.com/typester/Test-RedisServer).
9
11
 
10
12
 
@@ -13,10 +15,22 @@ It is a Ruby clone of [Test::RedisServer](https://github.com/typester/Test-Redis
13
15
  require 'redis'
14
16
  require 'redispot'
15
17
 
18
+ # Using redis-server instance within a block
19
+ redis = nil
16
20
  Redispot::Server.new do |connect_info|
17
21
  redis = Redis.new(connect_info)
18
22
  redis.ping # => "PONG"
19
23
  end
24
+
25
+ redis.ping # => Error!
26
+
27
+ # or start it manually
28
+
29
+ redispot = Redispot::Server.new
30
+ redis = Redis.new(redispot.start)
31
+ redis.ping # => "PONG"
32
+ redispot.stop
33
+ redis.ping # => Error!
20
34
  ```
21
35
 
22
36
 
@@ -25,7 +39,7 @@ end
25
39
  Create a new instance, and start redis-server if block given.
26
40
 
27
41
  ```ruby
28
- redis_server = Redispot::Server.new(options)
42
+ redispot = Redispot::Server.new(options)
29
43
 
30
44
  # or
31
45
 
@@ -72,11 +86,35 @@ Available options are:
72
86
  Start redis-server instance manually.
73
87
 
74
88
  ```ruby
75
- server = Redispot::Server.new
76
- server.start do |connect_info|
89
+ redispot = Redispot::Server.new
90
+
91
+ redispot.start do |connect_info|
77
92
  redis = Redis.new(connect_info)
78
93
  redis.ping # => "PONG"
79
94
  end
95
+
96
+ # or
97
+
98
+ redis = Redis.new(redispot.start)
99
+ # ... do anything
100
+ redispot.stop
101
+ ```
102
+
103
+
104
+ ### Redispot::Server#stop
105
+ Stop redis-server instance.
106
+
107
+ This method is automatically called from object destructor.
108
+
109
+
110
+ ### Redispot::Server#connect_info
111
+ Return connection info for client library to connect this redis-server instance.
112
+
113
+ This parameter is designed to pass directly to Redis module.
114
+
115
+ ```ruby
116
+ redispot = Redispot::Server.new
117
+ redis = Redis.new(redispot.connect_info)
80
118
  ```
81
119
 
82
120
 
@@ -0,0 +1,25 @@
1
+ module Redispot
2
+ module Refinements
3
+ refine Object do
4
+ def blank?
5
+ respond_to?(:empty?) ? !!empty? : !self
6
+ end
7
+
8
+ def present?
9
+ !blank?
10
+ end
11
+
12
+ def presence
13
+ self if present?
14
+ end
15
+ end
16
+
17
+ refine Hash do
18
+ def symbolize_keys
19
+ each_key.each_with_object(Hash.new) do |key, memo|
20
+ memo[key.to_sym] = fetch(key)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,27 +1,8 @@
1
- require 'fileutils'
2
1
  require 'timeout'
3
- require 'tmpdir'
4
2
 
5
3
  module Redispot
6
4
  class Server
7
-
8
- # Destractor
9
- #
10
- # @private
11
- def self.destroy (pid, owner_pid, timeout)
12
- Proc.new do
13
- return if pid.nil? || owner_pid != Process.pid
14
-
15
- signals = [:TERM, :INT, :KILL]
16
-
17
- begin
18
- Process.kill(signals.shift, pid)
19
- Timeout.timeout(timeout) { Process.waitpid(pid) }
20
- rescue Timeout::Error
21
- retry
22
- end
23
- end
24
- end
5
+ using Refinements
25
6
 
26
7
  # Create a new instance, and start redis-server if block given.
27
8
  #
@@ -40,10 +21,18 @@ module Redispot
40
21
  @executable = 'redis-server'
41
22
  @owner_pid = Process.pid
42
23
  @pid = nil
43
- @workdir = nil
44
- @config = config
24
+ @config = config.symbolize_keys
45
25
  @timeout = timeout
46
- @tmpdir = tmpdir.nil? ? nil : File.expand_path(tmpdir)
26
+ @workdir = WorkingDirectory.new(tmpdir)
27
+
28
+ if @config[:port].nil? && @config[:unixsocket].nil?
29
+ @config[:unixsocket] = "#{@workdir}/redis.sock"
30
+ @config[:port] = 0
31
+ end
32
+
33
+ if @config[:dir].nil?
34
+ @config[:dir] = @workdir
35
+ end
47
36
 
48
37
  if @config[:loglevel].to_s == 'warning'
49
38
  $stderr.puts 'Redispot::Server does not support "loglevel warning", using "notice" instead.'
@@ -53,117 +42,152 @@ module Redispot
53
42
  start(&block) if block
54
43
  end
55
44
 
56
- # Start redis-server instance within a given block.
45
+ # Start redis-server instance manually.
46
+ # If block given, the redis instance is available only within a block.
47
+ # Users must call Redispot::Server#stop they have called Redispot::Server#start without block.
57
48
  #
58
49
  # @example
59
- # redis_server = Redispot::Server.new
60
- # redis_server.start do |connect_info|
50
+ # redispot.start do |connect_info|
61
51
  # redis = Redis.new(connect_info)
62
52
  # assert_equal('PONG', redis.ping)
63
53
  # end
64
54
  #
65
- # @yield [connect_info] Connection info for client library to connect this redis-server instance.
66
- # @yieldparam connect_info [Hash] This parameter is designed to pass directly to Redis module.
55
+ # # or
56
+ #
57
+ # connect_info = redispot.start
58
+ #
59
+ # @overload start { ... }
60
+ # @yield [connect_info] Connection info for client library to connect this redis-server instance.
61
+ # @yieldparam connect_info [Hash] This parameter is designed to pass directly to Redis module.
62
+ # @overload start
63
+ # @return connect_info [Hash] This parameter is designed to pass directly to Redis module.
67
64
  def start
68
- Dir.mktmpdir(nil, @tmpdir) do |workdir|
69
- @workdir = workdir
70
- yield start_redis_server
65
+ return if @pid
66
+ start_process
67
+
68
+ if block_given?
69
+ yield connect_info
71
70
  stop
71
+ else
72
+ connect_info
72
73
  end
73
74
  end
74
75
 
75
- private
76
- def start_redis_server
77
- @pid = fork_process
76
+ # Stop redis-server.
77
+ # This method is automatically called from object destructor.
78
+ #
79
+ def stop
80
+ return unless @pid
78
81
 
79
- Timeout.timeout(@timeout) do
80
- loop do
81
- if !Process.waitpid(@pid, Process::WNOHANG).nil?
82
- @pid = nil
83
- raise RuntimeError, "failed to launch redis-server\n#{File.read(logfile)}"
84
- else
85
- if File.read(logfile) =~ /The server is now ready to accept connections/
86
- ObjectSpace.define_finalizer(self, proc { stop })
87
- return connect_info
88
- end
89
- end
82
+ signals = [:TERM, :INT, :KILL]
90
83
 
91
- sleep 0.1
92
- end
84
+ begin
85
+ Process.kill(signals.shift, @pid)
86
+ Timeout.timeout(@timeout) { Process.waitpid(@pid) }
87
+ rescue Timeout::Error => error
88
+ retry unless signals.empty?
89
+ raise error
93
90
  end
94
- rescue Timeout::Error
95
- stop if @pid
96
- raise RuntimeError, "failed to launch redis-server\n#{File.read(logfile)}"
97
- end
98
91
 
99
- def fork_process
100
- File.open(logfile, 'w') do |fh|
101
- Process.fork do
102
- begin
103
- exec @executable, config_file, out: fh, err: fh
104
- rescue => error
105
- $stderr.puts "exec failed: #{error}"
106
- exit error.errno
107
- end
108
- end
109
- end
92
+ @pid = nil
93
+
94
+ ObjectSpace.undefine_finalizer(self)
110
95
  end
111
96
 
97
+ # Return connection info for client library to connect this redis-server instance.
98
+ #
99
+ # @example
100
+ # redispot = Redispot::Server.new
101
+ # redis = Redis.new(redispot.connect_info)
102
+ #
103
+ # @return [String] This parameter is designed to pass directly to Redis module.
112
104
  def connect_info
113
- return unless @pid
114
-
115
- host = config[:bind].nil? ? '0.0.0.0' : config[:bind]
116
- port = config[:port]
105
+ host = @config[:bind].presence || '0.0.0.0'
106
+ port = @config[:port]
117
107
 
118
108
  if port.is_a?(Fixnum) && port > 0
119
109
  { url: "redis://#{host}:#{port}/" }
120
110
  else
121
- { path: config[:unixsocket] }
111
+ { path: @config[:unixsocket] }
122
112
  end
123
113
  end
124
114
 
125
- def stop
126
- return if @pid.nil? || @owner_pid != Process.pid
127
-
128
- signals = [:TERM, :INT, :KILL]
115
+ private
116
+ #
117
+ def start_process
118
+ logfile = "#{@workdir}/redis-server.log"
129
119
 
130
- begin
131
- Process.kill(signals.shift, @pid)
132
- Timeout.timeout(@timeout) { Process.waitpid(@pid) }
133
- rescue Timeout::Error
134
- retry
135
- end
120
+ execute_redis_server(logfile)
121
+ wait_redis_server(logfile)
122
+ ObjectSpace.define_finalizer(self, Killer.new(@pid, @timeout))
123
+ end
136
124
 
137
- @pid = nil
138
- @workdir = nil
125
+ #
126
+ # @param logfile [Pathname]
127
+ def execute_redis_server (logfile)
128
+ File.open(logfile, 'a') do |fh|
129
+ @pid = Process.fork do
130
+ configfile = "#{@workdir}/redis.conf"
131
+ File.write(configfile, config_string)
139
132
 
140
- ObjectSpace.undefine_finalizer(self)
133
+ begin
134
+ Kernel.exec(@executable, configfile, out: fh, err: fh)
135
+ rescue SystemCallError => error
136
+ $stderr.puts "exec failed: #{error}"
137
+ exit error.errno
138
+ end
139
+ end
140
+ end
141
+ rescue SystemCallError => error
142
+ $stderr.puts "failed to create log file: #{error}"
141
143
  end
142
144
 
143
- def logfile
144
- "#{@workdir}/redis.log"
145
+ #
146
+ # @param logfile [Pathname]
147
+ def wait_redis_server (logfile)
148
+ Timeout.timeout(@timeout) do
149
+ while Process.waitpid(@pid, Process::WNOHANG).nil?
150
+ return if File.read(logfile) =~ /the server is now ready to accept connections/i
151
+ sleep 0.1
152
+ end
153
+
154
+ @pid = nil
155
+ raise Timeout::Error
156
+ end
157
+ rescue Timeout::Error
158
+ raise RuntimeError, "failed to launch redis-server\n#{File.read(logfile)}"
145
159
  end
146
160
 
147
- def config_file
148
- config_string = config.inject('') do |memo, (key, value)|
161
+ #
162
+ # @return [String]
163
+ def config_string
164
+ @config.each_with_object(String.new) do |(key, value), memo|
149
165
  next if value.to_s.empty?
150
- memo += "#{key} #{value}\n"
166
+ memo << "#{key} #{value}\n"
151
167
  end
168
+ end
152
169
 
153
- "#{@workdir}/redis.conf".tap do |config_path|
154
- File.write(config_path, config_string)
170
+
171
+ class Killer # :nodoc
172
+ def initialize (redis_pid, timeout = 3)
173
+ @owner_pid = Process.pid
174
+ @redis_pid = redis_pid
175
+ @timeout = timeout
155
176
  end
156
- end
157
177
 
158
- def config
159
- default_config.merge(@config)
160
- end
178
+ def call (*args)
179
+ return if @owner_pid != Process.pid
180
+
181
+ signals = [:TERM, :INT, :KILL]
161
182
 
162
- def default_config
163
- {
164
- unixsocket: "#{@workdir}/redis.sock",
165
- dir: "#{@workdir}/"
166
- }
183
+ begin
184
+ Process.kill(signals.shift, pid)
185
+ Timeout.timeout(timeout) { Process.waitpid(pid) }
186
+ rescue Timeout::Error => error
187
+ retry unless signals.empty?
188
+ raise error
189
+ end
190
+ end
167
191
  end
168
192
 
169
193
  end
@@ -1,3 +1,3 @@
1
1
  module Redispot
2
- VERSION = '0.1.1'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -0,0 +1,43 @@
1
+ require 'fileutils'
2
+ require 'tmpdir'
3
+
4
+ module Redispot
5
+ class WorkingDirectory
6
+
7
+ # Create a temporary directory
8
+ #
9
+ # @param basedir [String]
10
+ def initialize (basedir = nil)
11
+ @directory = File.expand_path(Dir.mktmpdir(nil, basedir))
12
+ ObjectSpace.define_finalizer(self, Remover.new(@directory))
13
+ end
14
+
15
+ # Delete the temporary directory
16
+ #
17
+ def delete
18
+ FileUtils.remove_entry_secure(@directory)
19
+ ObjectSpace.undefine_finalizer(self)
20
+ rescue Errno::ENOENT
21
+ end
22
+
23
+ # Returns path to the temporary directory
24
+ #
25
+ # @return [String]
26
+ def to_s
27
+ @directory
28
+ end
29
+
30
+ class Remover # :nodoc
31
+ def initialize (directory)
32
+ @pid = Process.pid
33
+ @directory = directory
34
+ end
35
+
36
+ def call (*args)
37
+ return if @pid != Process.pid
38
+ FileUtils.remove_entry_secure(@directory)
39
+ rescue Errno::ENOENT
40
+ end
41
+ end
42
+ end
43
+ end
data/lib/redispot.rb CHANGED
@@ -1,2 +1,4 @@
1
+ require 'redispot/refinements'
1
2
  require 'redispot/server'
2
- require 'redispot/version.rb'
3
+ require 'redispot/working_directory'
4
+ require 'redispot/version'
data/redispot.gemspec CHANGED
@@ -17,10 +17,11 @@ Gem::Specification.new do |spec|
17
17
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
18
  spec.require_paths = ['lib']
19
19
 
20
- spec.required_ruby_version = '>= 2.0.0'
20
+ spec.required_ruby_version = '>= 2.1.0'
21
21
 
22
22
  spec.add_development_dependency 'bundler', '~> 1.10'
23
23
  spec.add_development_dependency 'rake', '~> 10.0'
24
+ spec.add_development_dependency 'codeclimate-test-reporter'
24
25
  spec.add_development_dependency 'pry'
25
26
  spec.add_development_dependency 'redis'
26
27
  spec.add_development_dependency 'test-unit'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redispot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - hatyuki
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-07-09 00:00:00.000000000 Z
11
+ date: 2015-07-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: codeclimate-test-reporter
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: pry
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -102,6 +116,7 @@ executables: []
102
116
  extensions: []
103
117
  extra_rdoc_files: []
104
118
  files:
119
+ - ".envrc"
105
120
  - ".gitignore"
106
121
  - ".travis.yml"
107
122
  - Gemfile
@@ -111,8 +126,10 @@ files:
111
126
  - bin/console
112
127
  - bin/setup
113
128
  - lib/redispot.rb
129
+ - lib/redispot/refinements.rb
114
130
  - lib/redispot/server.rb
115
131
  - lib/redispot/version.rb
132
+ - lib/redispot/working_directory.rb
116
133
  - redispot.gemspec
117
134
  homepage: https://github.com/hatyuki/redispot-rb
118
135
  licenses:
@@ -126,7 +143,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
126
143
  requirements:
127
144
  - - ">="
128
145
  - !ruby/object:Gem::Version
129
- version: 2.0.0
146
+ version: 2.1.0
130
147
  required_rubygems_version: !ruby/object:Gem::Requirement
131
148
  requirements:
132
149
  - - ">="