kjess 1.0.0 → 1.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.
@@ -35,7 +35,8 @@ easiest way to contribute.
35
35
 
36
36
  # Contributors
37
37
 
38
- * Jeremy Hinegardner
38
+ * Jeremy Hinegardner <https://github.com/copiousfreetime>
39
+ * Eric Lindvall <https://github.com/eric>
39
40
 
40
41
  [GitHub Account]: https://github.com/signup/free "GitHub Signup"
41
42
  [GitHub Issues]: https://github.com/copiousfreetime/kjess/issues "KJess Issues"
@@ -1,4 +1,15 @@
1
1
  = KJess Changlog
2
+
3
+ == Version 1.1.0 - 2013-01-08
4
+
5
+ * Allow the setting of the kestrel server processes ports for testing (eric)
6
+ * Update development dependencies, new version and missing gems
7
+ * Re-wrap all network errors with KJess Errors (eric)
8
+ * Ensure clients are not shared across fork() (eric)
9
+ * Enforce boolean return value from KJess::Client#set (eric)
10
+ * Added socket timeout feature (eric)
11
+ * Added support for tcp keepalive
12
+
2
13
  == Version 1.0.0 - 2012-10-31
3
14
 
4
15
  * Initial Release - Yeah!
@@ -36,10 +36,12 @@ lib/kjess/response/not_stored.rb
36
36
  lib/kjess/response/reloaded_config.rb
37
37
  lib/kjess/response/server_error.rb
38
38
  lib/kjess/response/stats.rb
39
+ lib/kjess/response/status.rb
39
40
  lib/kjess/response/stored.rb
40
41
  lib/kjess/response/unknown.rb
41
42
  lib/kjess/response/value.rb
42
43
  lib/kjess/response/version.rb
44
+ lib/kjess/socket.rb
43
45
  lib/kjess/stats_cache.rb
44
46
  spec/client_spec.rb
45
47
  spec/kestrel_server.rb
data/Rakefile CHANGED
@@ -19,13 +19,7 @@ namespace :develop do
19
19
  require 'rubygems/dependency_installer'
20
20
  installer = Gem::DependencyInstaller.new
21
21
 
22
- # list these here instead of gem dependencies since there is not a way to
23
- # specify ruby version specific dependencies
24
- if RUBY_VERSION < "1.9.2"
25
- Util.platform_gemspec.add_development_dependency( 'rcov', '~> 0.9.11' )
26
- else
27
- Util.platform_gemspec.add_development_dependency( 'simplecov', '~> 0.6.4' )
28
- end
22
+ Util.set_coverage_gem
29
23
 
30
24
  puts "Installing gem depedencies needed for development"
31
25
  Util.platform_gemspec.dependencies.each do |dep|
@@ -194,14 +188,13 @@ This.gemspec['ruby'] = Gem::Specification.new do |spec|
194
188
  "--markup", "tomdoc" ]
195
189
 
196
190
  # The Runtime Dependencies
197
- # FIXME
198
- # spec.add_dependency( 'map', '~> 6.2.0')
199
191
 
200
192
  # The Development Dependencies
201
- spec.add_development_dependency( 'rake' , '~> 0.9.2.2')
202
- spec.add_development_dependency( 'minitest' , '~> 3.3.0' )
203
- spec.add_development_dependency( 'rdoc' , '~> 3.12' )
204
- spec.add_development_dependency( 'zip' , "~> 2.0.2" )
193
+ spec.add_development_dependency( 'rake' , '~> 10.0.3')
194
+ spec.add_development_dependency( 'minitest' , '~> 4.4.0' )
195
+ spec.add_development_dependency( 'rdoc' , '~> 3.12' )
196
+ spec.add_development_dependency( 'zip' , '~> 2.0.2' )
197
+ spec.add_development_dependency( 'json' , '~> 1.7.6' )
205
198
  end
206
199
 
207
200
 
@@ -211,6 +204,7 @@ This.gemspec_file = "#{This.name}.gemspec"
211
204
  # Really this is only here to support those who use bundler
212
205
  desc "Build the #{This.name}.gemspec file"
213
206
  task :gemspec do
207
+ Util.set_coverage_gem
214
208
  File.open( This.gemspec_file, "wb+" ) do |f|
215
209
  f.write Util.platform_gemspec.to_ruby
216
210
  end
@@ -261,7 +255,13 @@ end
261
255
  # Load the extra rake tasks
262
256
  #------------------------------------------------------------------------------
263
257
  $: << "." unless $:.include?(".")
264
- load 'tasks/kestrel.rake'
258
+ begin
259
+ load 'tasks/kestrel.rake'
260
+ rescue LoadError => le
261
+ Util.task_warning( 'kestrel' )
262
+ end
263
+
264
+
265
265
 
266
266
  #------------------------------------------------------------------------------
267
267
  # Rakefile Support - This is all the guts and utility methods that are
@@ -320,6 +320,19 @@ BEGIN {
320
320
  def self.platform_gemspec
321
321
  This.gemspec[This.platform]
322
322
  end
323
+
324
+ def self.set_coverage_gem
325
+ # list these here instead of gem dependencies since there is not a way to
326
+ # specify ruby version specific dependencies
327
+ g, v = 'simplecov', '~> 0.7.1'
328
+ if RUBY_VERSION < "1.9.2"
329
+ g, v = 'rcov', '~> 1.0.0'
330
+ end
331
+
332
+ if Util.platform_gemspec.dependencies.none? { |s| s.name == g } then
333
+ Util.platform_gemspec.add_development_dependency( g, v )
334
+ end
335
+ end
323
336
  end
324
337
 
325
338
  # Hold all the metadata about this project
@@ -1,5 +1,5 @@
1
1
  module KJess
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.0"
3
3
  end
4
4
 
5
5
  require 'kjess/connection'
@@ -30,7 +30,7 @@ module KJess
30
30
  @port = merged[:port]
31
31
  @admin_port = merged[:admin_port]
32
32
  @stats_cache = StatsCache.new( self, merged[:stats_cache_expiration] )
33
- @connection = KJess::Connection.new( host, port )
33
+ @connection = KJess::Connection.new( host, port, merged )
34
34
  end
35
35
 
36
36
  # Public: Disconnect from the Kestrel server
@@ -77,7 +77,9 @@ module KJess
77
77
  # Returns true if successful, false otherwise
78
78
  def set( queue_name, item, expiration = 0 )
79
79
  s = KJess::Request::Set.new( :queue_name => queue_name, :data => item, :expiration => expiration )
80
- send_recv( s )
80
+ resp = send_recv( s )
81
+
82
+ return KJess::Response::Stored === resp
81
83
  end
82
84
 
83
85
  # Public: Retrieve an item from the given queue
@@ -94,10 +96,18 @@ module KJess
94
96
  def get( queue_name, opts = {} )
95
97
  opts = opts.merge( :queue_name => queue_name )
96
98
  g = KJess::Request::Get.new( opts )
97
- resp = send_recv( g )
98
99
 
99
- return resp.data if KJess::Response::Value === resp
100
- return nil
100
+ if opts[:wait_for]
101
+ wait_for_in_seconds = opts[:wait_for] / 1000
102
+ else
103
+ wait_for_in_seconds = 0.1
104
+ end
105
+
106
+ connection.with_additional_read_timeout(wait_for_in_seconds) do
107
+ resp = send_recv( g )
108
+ return resp.data if KJess::Response::Value === resp
109
+ return nil
110
+ end
101
111
  end
102
112
 
103
113
  # Public: Reserve the next item on the queue
@@ -176,17 +186,27 @@ module KJess
176
186
  #
177
187
  # Returns true if the queue was flushed.
178
188
  def flush( queue_name )
179
- req = KJess::Request::Flush.new( :queue_name => queue_name )
180
- resp = send_recv( req )
181
- return KJess::Response::End === resp
189
+ # It can take a long time to flush all of the messages
190
+ # on a server, so we'll set the read timeout to something
191
+ # much higher than usual.
192
+ connection.with_additional_read_timeout(60) do
193
+ req = KJess::Request::Flush.new( :queue_name => queue_name )
194
+ resp = send_recv( req )
195
+ return KJess::Response::End === resp
196
+ end
182
197
  end
183
198
 
184
199
  # Public: Remove all items from all queues on the kestrel server
185
200
  #
186
201
  # Returns true.
187
202
  def flush_all
188
- resp = send_recv( KJess::Request::FlushAll.new )
189
- return KJess::Response::End === resp
203
+ # It can take a long time to flush all of the messages
204
+ # on a server, so we'll set the read timeout to something
205
+ # much higher than usual.
206
+ connection.with_additional_read_timeout(60) do
207
+ resp = send_recv( KJess::Request::FlushAll.new )
208
+ return KJess::Response::End === resp
209
+ end
190
210
  end
191
211
 
192
212
  # Public: Have Kestrel reload its config.
@@ -215,7 +235,7 @@ module KJess
215
235
  #
216
236
  # Returns a String.
217
237
  def status( update_to = nil )
218
- resp = send_recv( KJess::Request::Status.new( update_to ) )
238
+ resp = send_recv( KJess::Request::Status.new( :update_to => update_to ) )
219
239
  raise KJess::Error, "Status command is not supported" if KJess::Response::ClientError === resp
220
240
  return resp.message
221
241
  end
@@ -236,6 +256,8 @@ module KJess
236
256
  # Returns a Hash
237
257
  def stats!
238
258
  stats = send_recv( KJess::Request::Stats.new )
259
+ raise KJess::Error, "Problem receiving stats: #{stats.inspect}" unless KJess::Response::Stats === stats
260
+
239
261
  h = stats.data
240
262
  dump_stats = send_recv( KJess::Request::DumpStats.new )
241
263
  h['queues'] = Hash.new
@@ -253,8 +275,7 @@ module KJess
253
275
  def ping
254
276
  stats
255
277
  true
256
- rescue Errno::ECONNREFUSED => e
257
- puts e
278
+ rescue Errno::ECONNREFUSED
258
279
  false
259
280
  end
260
281
 
@@ -1,65 +1,104 @@
1
1
  require 'fcntl'
2
- require 'socket'
3
2
  require 'resolv'
4
3
  require 'resolv-replace'
5
4
  require 'kjess/error'
5
+ require 'kjess/socket'
6
6
 
7
7
  module KJess
8
8
  # Connection
9
9
  class Connection
10
10
  class Error < KJess::Error; end
11
11
 
12
- CRLF = "\r\n"
12
+ # Public: The hostname/ip address to connect to.
13
+ def host
14
+ socket.host
15
+ end
13
16
 
14
- # Public:
15
- # The hostname/ip address to connect to
16
- attr_reader :host
17
+ # Public: The port number to connect to. Default 22133
18
+ def port
19
+ socket.port
20
+ end
17
21
 
18
- # Public
19
- # The port number to connect to. Default 22133
20
- attr_reader :port
22
+ # Public: The timeout for connecting in seconds. Defaults to 2
23
+ def connect_timeout
24
+ socket.connect_timeout
25
+ end
21
26
 
22
- def initialize( host, port = 22133 )
23
- @host = host
24
- @port = Float( port ).to_i
25
- @socket = nil
27
+ # Public: The timeout for reading in seconds. Defaults to 2
28
+ def read_timeout
29
+ socket.read_timeout
26
30
  end
27
31
 
28
- # Internal: Return the raw socket that is connected to the Kestrel server
29
- #
30
- # Returns the raw socket. If the socket is not connected it will connect and
31
- # then return it.
32
- #
33
- # Returns a TCPSocket
34
- def socket
35
- return @socket if @socket and not @socket.closed?
36
- return @socket = connect()
32
+ # Public: The timeout for writing in seconds. Defaults to 2
33
+ def write_timeout
34
+ socket.write_timeout
37
35
  end
38
36
 
39
- # Internal: Create the socket we use to talk to the Kestrel server
40
- #
41
- # Returns a TCPSocket
42
- def connect
43
- sock = TCPSocket.new( host, port )
37
+ # Internal: return thekeepalive timeout
38
+ def keepalive_active?
39
+ socket.keepalive_active?
40
+ end
44
41
 
45
- # close file descriptors if we exec or something like that
46
- sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
42
+ # Internal: return the keepalive count
43
+ # The keepalive count
44
+ def keepalive_count
45
+ socket.keepalive_count
46
+ end
47
47
 
48
- # Disable Nagle's algorithm
49
- sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
48
+ # Internal: return the keepalive interval
49
+ def keepalive_interval
50
+ socket.keepalive_interval
51
+ end
50
52
 
51
- # limit only to IPv4?
52
- # addr = ::Socket.getaddrinfo(host, nil, Socket::AF_INET)
53
- # sock = ::Socket.new(::Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
54
- # saddr = ::Socket.pack_sockaddr_in(port, addr[0][3])
53
+ # Internal: return the keepalive idle
54
+ def keepalive_idle
55
+ socket.keepalive_idle
56
+ end
55
57
 
56
- # tcp keepalive
57
- # :SOL_SOCKET, :SO_KEEPALIVE, :SOL_TCP, :TCP_KEEPIDLE, :TCP_KEEPINTVL, :TCP_KEEPCNT].all?{|c| Socket.const_defined? c}
58
- # @sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
59
- # @sock.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE, keepalive[:time])
60
- # @sock.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL, keepalive[:intvl])
61
- # @sock.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT, keepalive[:probes])
62
- return sock
58
+ # TODO: make port an option at next major version number change
59
+ def initialize( host, port = 22133, options = {} )
60
+ if port.is_a?(Hash)
61
+ options = port
62
+ port = 22133
63
+ end
64
+
65
+ @options = options.dup
66
+ @options[:host] = host
67
+ @options[:port] = Float( port ).to_i
68
+ @socket = nil
69
+ @pid = nil
70
+ @read_buffer = ''
71
+ end
72
+
73
+ # Internal: Adds time to the read timeout
74
+ #
75
+ # additional_timeout - additional number of seconds to the read timeout
76
+ #
77
+ # Returns nothing
78
+ def with_additional_read_timeout(additional_timeout, &block)
79
+ old_read_timeout = socket.read_timeout
80
+ socket.read_timeout += additional_timeout
81
+ block.call
82
+ ensure
83
+ @read_timeout = old_read_timeout
84
+ end
85
+
86
+ # Internal: Return the socket that is connected to the Kestrel server
87
+ #
88
+ # Returns the socket. If the socket is not connected it will connect and
89
+ # then return it.
90
+ #
91
+ # Make sure that we close the socket if we are not the same process that
92
+ # opened that socket to begin with.
93
+ #
94
+ # Returns a KJess::Socket
95
+ def socket
96
+ close if @pid && @pid != Process.pid
97
+ return @socket if @socket and not @socket.closed?
98
+ @socket = Socket.connect( @options )
99
+ @pid = Process.pid
100
+ @read_buffer = ''
101
+ return @socket
63
102
  end
64
103
 
65
104
  # Internal: close the socket if it is not already closed
@@ -67,6 +106,7 @@ module KJess
67
106
  # Returns nothing
68
107
  def close
69
108
  @socket.close if @socket and not @socket.closed?
109
+ @read_buffer = ''
70
110
  @socket = nil
71
111
  end
72
112
 
@@ -85,8 +125,11 @@ module KJess
85
125
  #
86
126
  # Returns nothing
87
127
  def write( msg )
88
- $stderr.write "--> #{msg}" if $DEBUG
128
+ $stderr.puts "--> #{msg}" if $DEBUG
89
129
  socket.write( msg )
130
+ rescue KJess::Error
131
+ close
132
+ raise
90
133
  end
91
134
 
92
135
  # Internal: read a single line from the socket
@@ -95,25 +138,44 @@ module KJess
95
138
  #
96
139
  # Returns a String
97
140
  def readline( eom = Protocol::CRLF )
98
- while line = socket.readline( eom ) do
99
- $stderr.write "<-- #{line}" if $DEBUG
141
+ while true
142
+ while (idx = @read_buffer.index(eom)) == nil
143
+ @read_buffer << socket.readpartial(10240)
144
+ end
145
+
146
+ line = @read_buffer.slice!(0, idx + eom.length)
147
+ $stderr.puts "<-- #{line}" if $DEBUG
100
148
  break unless line.strip.length == 0
101
149
  end
102
150
  return line
151
+ rescue KJess::Error
152
+ close
153
+ raise
103
154
  rescue EOFError
104
155
  close
105
156
  return "EOF"
157
+ rescue => e
158
+ close
159
+ raise Error, "Could not read from #{host}:#{port}: #{e.class}: #{e.message}", e.backtrace
106
160
  end
107
161
 
108
162
  # Internal: Read from the socket
109
163
  #
110
- # args - this method takes the same arguments as IO#read
164
+ # nbytes - this method takes the number of bytes to read
111
165
  #
112
166
  # Returns what IO#read returns
113
- def read( *args )
114
- d = socket.read( *args )
115
- $stderr.puts "<-- #{d}" if $DEBUG
116
- return d
167
+ def read( nbytes )
168
+ while @read_buffer.length < nbytes
169
+ @read_buffer << socket.readpartial(nbytes - @read_buffer.length)
170
+ end
171
+
172
+ result = @read_buffer.slice!(0, nbytes)
173
+
174
+ $stderr.puts "<-- #{result}" if $DEBUG
175
+ return result
176
+ rescue KJess::Error
177
+ close
178
+ raise
117
179
  end
118
180
  end
119
181
  end
@@ -1,5 +1,5 @@
1
1
  module KJess
2
- # Protocl is the base class that all Kestrel requests and responses are
2
+ # Protocol is the base class that all Kestrel requests and responses are
3
3
  # developed on. it defines the DSL for creating the Request and Response
4
4
  # objects that make up the Protocol.
5
5
  #
@@ -14,11 +14,12 @@ module KJess
14
14
  #
15
15
  # Returns the name
16
16
  def keyword( name = nil )
17
+ @keyword = nil unless defined? @keyword
17
18
  if name then
18
19
  register( name )
19
20
  @keyword = name
20
21
  end
21
- @keyword
22
+ @keyword ||= nil
22
23
  end
23
24
 
24
25
  # Internal: define or return the arity of this protocol item
@@ -3,6 +3,13 @@ class KJess::Request
3
3
  class Status < KJess::Request
4
4
  keyword 'STATUS'
5
5
  arity 1
6
- #valid_responses [ KJess::Response::Eof ]
6
+ valid_responses [ KJess::Response::Status::Up, KJess::Response::Status::Down,
7
+ KJess::Response::Status::ReadOnly, KJess::Response::Status::Quiescent,
8
+ KJess::Response::End ]
9
+
10
+ def parse_options_to_args( opts )
11
+ [ opts[:update_to] ]
12
+ end
13
+
7
14
  end
8
15
  end
@@ -70,6 +70,7 @@ require 'kjess/response/not_stored'
70
70
  require 'kjess/response/reloaded_config'
71
71
  require 'kjess/response/server_error'
72
72
  require 'kjess/response/stats'
73
+ require 'kjess/response/status'
73
74
  require 'kjess/response/stored'
74
75
  require 'kjess/response/unknown'
75
76
  require 'kjess/response/value'
@@ -0,0 +1,19 @@
1
+ class KJess::Response
2
+ class Status < KJess::Response
3
+ class Up < KJess::Response::Status
4
+ keyword "UP"
5
+ end
6
+
7
+ class Down < KJess::Response::Status
8
+ keyword "DOWN"
9
+ end
10
+
11
+ class Quiescent < KJess::Response::Status
12
+ keyword "QUIESCENT"
13
+ end
14
+
15
+ class ReadOnly< KJess::Response::Status
16
+ keyword "READONLY"
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,291 @@
1
+ require 'socket'
2
+
3
+ module KJess
4
+ # Socket: A specialized socket that has been configure
5
+ class Socket
6
+ class Error < KJess::Error; end
7
+ class Timeout < Error; end
8
+
9
+ # Internal:
10
+ # The timeout for reading in seconds. Defaults to 2
11
+ attr_accessor :read_timeout
12
+
13
+ # Internal:
14
+ # The timeout for connecting in seconds. Defaults to 2
15
+ attr_reader :connect_timeout
16
+
17
+ # Internal:
18
+ # The timeout for writing in seconds. Defaults to 2
19
+ attr_reader :write_timeout
20
+
21
+ # Internal:
22
+ # The host this socket is connected to
23
+ attr_reader :host
24
+
25
+ # Internal:
26
+ # The port this socket is connected to
27
+ attr_reader :port
28
+
29
+ # Internal
30
+ #
31
+ # Used for setting TCP_KEEPIDLE: overrides tcp_keepalive_time for a single
32
+ # socket.
33
+ #
34
+ # http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/usingkeepalive.html
35
+ #
36
+ # tcp_keepalive_time:
37
+ #
38
+ # The interval between the last data packet sent (simple ACKs are not
39
+ # considered data) and the first keepalive probe; after the connection is
40
+ # marked to need keepalive, this counter is not used any further.
41
+ attr_reader :keepalive_idle
42
+
43
+ # Internal
44
+ #
45
+ # Used for setting TCP_KEEPINTVL: overrides tcp_keepalive_intvl for a single
46
+ # socket.
47
+ #
48
+ # http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/usingkeepalive.html
49
+ #
50
+ # tcp_keepalive_intvl:
51
+ #
52
+ # The interval between subsequential keepalive probes, regardless of what
53
+ # the connection has exchanged in the meantime.
54
+ attr_reader :keepalive_interval
55
+
56
+ # Internal
57
+ #
58
+ # Used for setting TCP_KEEPCNT: overrides tcp_keepalive_probes for a single
59
+ # socket.
60
+ #
61
+ # http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/usingkeepalive.html
62
+ #
63
+ # tcp_keepalive_probes:
64
+ #
65
+ # The number of unacknowledged probes to send before considering the
66
+ # connection dead and notifying the application layer.
67
+ attr_reader :keepalive_count
68
+
69
+
70
+ # Internal: Create and connect to the given location.
71
+ #
72
+ # options, same as Constructor
73
+ #
74
+ # Returns an instance of KJess::Socket
75
+ def self.connect( options = {} )
76
+ s = Socket.new( options )
77
+ s.connect
78
+ return s
79
+ end
80
+
81
+ # Internal: Creates a new KJess::Socket
82
+ def initialize( options = {} )
83
+ @host = options.fetch(:host)
84
+ @port = options.fetch(:port)
85
+ @connect_timeout = options.fetch(:connect_timeout , 2)
86
+ @read_timeout = options.fetch(:read_timeout , 2)
87
+ @write_timeout = options.fetch(:write_timeout , 2)
88
+ @keepalive_active = options.fetch(:keepalive_active , true)
89
+ @keepalive_idle = options.fetch(:keepalive_idle , 60)
90
+ @keepalive_interval = options.fetch(:keepalive_interval, 30)
91
+ @keepalive_count = options.fetch(:keepalive_count , 5)
92
+ @socket = nil
93
+ end
94
+
95
+ # Internal: Return whether or not the keepalive_active flag is set.
96
+ def keepalive_active?
97
+ @keepalive_active
98
+ end
99
+
100
+ # Internal: Low level socket allocation and option configuration
101
+ #
102
+ # Using the options from the initializer, a new ::Socket is created that
103
+ # is:
104
+ #
105
+ # TCP, IPv4 only, autoclosing on exit, nagle's algorithm is disabled and has
106
+ # TCP Keepalive options set if keepalive is supported.
107
+ #
108
+ # Returns a new ::Socket instance
109
+ def blank_socket
110
+ sock = ::Socket.new(::Socket::AF_INET, ::Socket::SOCK_STREAM, 0)
111
+
112
+ # close file descriptors if we exec
113
+ sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
114
+
115
+ # Disable Nagle's algorithm
116
+ sock.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
117
+
118
+ if using_keepalive? then
119
+ sock.setsockopt( ::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE , true )
120
+ sock.setsockopt( ::Socket::SOL_TCP, ::Socket::TCP_KEEPIDLE , keepalive_idle )
121
+ sock.setsockopt( ::Socket::SOL_TCP, ::Socket::TCP_KEEPINTVL, keepalive_interval)
122
+ sock.setsockopt( ::Socket::SOL_TCP, ::Socket::TCP_KEEPCNT , keepalive_count)
123
+ end
124
+
125
+ return sock
126
+ end
127
+
128
+ # Internal: Return the connected raw Socket.
129
+ #
130
+ # If the socket is closed or non-existent it will create and connect again.
131
+ #
132
+ # Returns a ::Socket
133
+ def socket
134
+ return @socket unless closed?
135
+ @socket = connect()
136
+ end
137
+
138
+ # Internal: Closes the internal ::Socket
139
+ #
140
+ # Returns nothing
141
+ def close
142
+ @socket.close unless closed?
143
+ @socket = nil
144
+ end
145
+
146
+ # Internal: Return true the socket is closed.
147
+ def closed?
148
+ return true if @socket.nil?
149
+ return true if @socket.closed?
150
+ return false
151
+ end
152
+
153
+ # Internal:
154
+ #
155
+ # Connect to the remote host in a non-blocking fashion.
156
+ #
157
+ # Raise Error if there is a failure connecting.
158
+ #
159
+ # Return the ::Socket on success
160
+ def connect
161
+ # Calculate our timeout deadline
162
+ deadline = Time.now.to_f + connect_timeout
163
+
164
+ # Lookup destination address, we only want IPv4 , TCP
165
+ addrs = ::Socket.getaddrinfo(host, port, ::Socket::AF_INET, ::Socket::SOCK_STREAM )
166
+ errors = []
167
+ conn_error = lambda { raise errors.first }
168
+ sock = nil
169
+
170
+ addrs.find( conn_error ) do |addr|
171
+ sock = connect_or_error( addr, deadline, errors )
172
+ end
173
+ return sock
174
+ end
175
+
176
+ # Internal: Connect to the destination or raise an error.
177
+ #
178
+ # Connect to the address or capture the error of the connection
179
+ #
180
+ # addr - An address returned from Socket.getaddrinfo()
181
+ # deadline - the after which we should raise a timeout error
182
+ # errors - a collection of errors to append an error too should we have one.
183
+ #
184
+ # Make an attempt to connect to the given address. If it is successful,
185
+ # return the socket.
186
+ #
187
+ # Should the connection fail, append the exception to the errors array and
188
+ # return false.
189
+ #
190
+ def connect_or_error( addr, deadline, errors )
191
+ timeout = deadline - Time.now.to_f
192
+ raise Timeout, "Could not connect to #{host}:#{port}" if timeout <= 0
193
+ return connect_nonblock( addr, timeout )
194
+ rescue Error => e
195
+ errors << e
196
+ return false
197
+ end
198
+
199
+ # Internal: Connect to the give address within the timeout.
200
+ #
201
+ # Make an attempt to connect to a single address within the given timeout.
202
+ #
203
+ # Return the ::Socket when it is connected, or raise an Error if no
204
+ # connection was possible.
205
+ def connect_nonblock( addr, timeout )
206
+ sockaddr = ::Socket.pack_sockaddr_in(addr[1], addr[3])
207
+ sock = blank_socket()
208
+ sock.connect_nonblock( sockaddr )
209
+ return sock
210
+ rescue Errno::EINPROGRESS
211
+ if IO.select(nil, [sock], nil, timeout).nil? then
212
+ raise Timeout, "Could not connect to #{host}:#{port} within #{timeout} seconds"
213
+ end
214
+ return connect_nonblock_finalize( sock, sockaddr )
215
+ rescue => ex
216
+ raise Error, "Could not connect to #{host}:#{port}: #{ex.class}: #{ex.message}", ex.backtrace
217
+ end
218
+
219
+
220
+ # Internal: Make sure that a non-blocking connect has truely connected.
221
+ #
222
+ # Ensure that the given socket is actually connected to the given adddress.
223
+ #
224
+ # Returning the socket if it is and raising an Error if it isn't.
225
+ def connect_nonblock_finalize( sock, sockaddr )
226
+ sock.connect_nonblock( sockaddr )
227
+ rescue Errno::EISCONN
228
+ return sock
229
+ rescue => ex
230
+ raise Error, "Could not connect to #{host}:#{port}: #{ex.class}: #{ex.message}", ex.backtrace
231
+ end
232
+
233
+ # Internal: say if we are using TCP Keep Alive or not
234
+ #
235
+ # We will return true if the initialization options :keepalive_active is
236
+ # set to true, and if all the constants that are necessary to use TCP keep
237
+ # alive are defined.
238
+ #
239
+ # It may be the case that on some operating systems that the constants are
240
+ # not defined, so in that case we do not want to attempt to use tcp keep
241
+ # alive if we are unable to do so in any case.
242
+ #
243
+ # Returns true or false
244
+ def using_keepalive?
245
+ using = false
246
+ if keepalive_active? then
247
+ using = [ :SOL_SOCKET, :SO_KEEPALIVE, :SOL_TCP, :TCP_KEEPIDLE, :TCP_KEEPINTVL, :TCP_KEEPCNT].all? do |c|
248
+ ::Socket.const_defined? c
249
+ end
250
+ end
251
+ return using
252
+ end
253
+
254
+ # Internal: Read up to a maxlen of data from the socket and store it in outbuf
255
+ #
256
+ # maxlen - the maximum number of bytes to read from the socket
257
+ # outbuf - the buffer in which to store the bytes.
258
+ #
259
+ # Returns the bytes read
260
+ def readpartial(maxlen, outbuf = nil)
261
+ return socket.read_nonblock(maxlen, outbuf)
262
+ rescue Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::ECONNRESET
263
+ if IO.select([@socket], nil, nil, read_timeout)
264
+ retry
265
+ else
266
+ raise Timeout, "Could not read from #{host}:#{port} in #{read_timeout} seconds"
267
+ end
268
+ end
269
+
270
+ # Internal: Write the given data to the socket
271
+ #
272
+ # buf - the data to write to the socket.
273
+ #
274
+ # Raises an error if it is unable to write the data to the socket within the
275
+ # write_timeout.
276
+ #
277
+ # returns nothing
278
+ def write( buf )
279
+ until buf.length == 0
280
+ written = socket.write_nonblock(buf)
281
+ buf = buf[written, buf.length]
282
+ end
283
+ rescue Errno::EWOULDBLOCK, Errno::EINTR, Errno::EAGAIN, Errno::ECONNRESET
284
+ if IO.select(nil, [socket], nil, write_timeout)
285
+ retry
286
+ else
287
+ raise Timeout, "Could not write to #{host}:#{port} in #{write_timeout} seconds"
288
+ end
289
+ end
290
+ end
291
+ end
@@ -1,15 +1,28 @@
1
1
  require 'spec_helper'
2
2
 
3
- #$DEBUG = true
4
3
  describe KJess::Client do
5
4
  before do
6
- @client = KJess::Client.new
5
+ @client_version = "2.4.1"
6
+ @client = KJess::Spec.kjess_client()
7
7
  end
8
8
 
9
9
  after do
10
10
  KJess::Spec.reset_server( @client )
11
11
  end
12
12
 
13
+ describe "#initialize" do
14
+ it "can set keepalive parameters" do
15
+ client = KJess::Client.new( :port => KJess::Spec.memcache_port,
16
+ :keepalive_active => true,
17
+ :keepalive_interval => 1,
18
+ :keepalive_idle => 900,
19
+ :keepalive_count => 42)
20
+ client.connection.keepalive_interval.must_equal 1
21
+ client.connection.keepalive_idle.must_equal 900
22
+ client.connection.keepalive_count.must_equal 42
23
+ end
24
+ end
25
+
13
26
  describe "connection" do
14
27
  it "knows if it is connected" do
15
28
  @client.ping
@@ -26,13 +39,13 @@ describe KJess::Client do
26
39
 
27
40
  describe "#version" do
28
41
  it "knows the version of the server" do
29
- @client.version.must_equal "2.3.4"
42
+ @client.version.must_equal @client_version
30
43
  end
31
44
  end
32
45
 
33
46
  describe "#stats" do
34
47
  it "can see the stats on an empty server" do
35
- @client.stats['version'].must_equal '2.3.4'
48
+ @client.stats['version'].must_equal @client_version
36
49
  end
37
50
 
38
51
  it "sees the stats on a server with queues" do
@@ -65,6 +78,12 @@ describe KJess::Client do
65
78
  end
66
79
  @client.get( 'set_q_2' ).must_equal 'setspec2'
67
80
  end
81
+
82
+ it 'a really long binary item' do
83
+ binary = (0..255).to_a.pack('c*') * 100
84
+ @client.set 'set_bin_q', binary
85
+ @client.get('set_bin_q').must_equal binary
86
+ end
68
87
  end
69
88
 
70
89
  describe "#get" do
@@ -253,7 +272,14 @@ describe KJess::Client do
253
272
 
254
273
  describe "#status" do
255
274
  it "returns the server status" do
256
- lambda { @client.status }.must_raise KJess::ClientError
275
+ @client.status.must_equal "UP"
276
+ end
277
+
278
+ it "can change the status" do
279
+ @client.status( "readonly" ).must_equal "END"
280
+ @client.status.must_equal "READONLY"
281
+ @client.status( "up" ).must_equal "END"
282
+ @client.status.must_equal "UP"
257
283
  end
258
284
  end
259
285
 
@@ -262,4 +288,67 @@ describe KJess::Client do
262
288
  @client.ping.must_equal true
263
289
  end
264
290
  end
291
+
292
+ describe "connecting to a server on a port that isn't listening" do
293
+ it "throws an exception" do
294
+ c = KJess::Connection.new '127.0.0.1', 65521
295
+ lambda { c.socket }.must_raise KJess::Socket::Error
296
+ end
297
+ end
298
+
299
+ describe "connecting to a server that isn't responding" do
300
+ it "throws an exception" do
301
+ c = KJess::Connection.new '127.1.1.1', 65521, :timeout => 0.5
302
+ lambda { c.socket }.must_raise KJess::Socket::Timeout
303
+ end
304
+ end
305
+
306
+ describe "reading for longer than the timeout" do
307
+ it "throws an exception" do
308
+ q = Queue.new
309
+ t = Thread.new do
310
+ begin
311
+ server = TCPServer.new 65520
312
+ q.enq :go
313
+ client = server.accept
314
+ Thread.stop
315
+ ensure
316
+ server.close rescue nil
317
+ client.close rescue nil
318
+ end
319
+ end
320
+
321
+ q.deq
322
+ c = KJess::Connection.new '127.0.0.1', 65520, :timeout => 0.5
323
+
324
+ lambda { c.readline }.must_raise KJess::Socket::Timeout
325
+
326
+ t.run
327
+ t.join
328
+ end
329
+ end
330
+
331
+ describe "writing for longer than the timeout" do
332
+ it "throws an exception" do
333
+ q = Queue.new
334
+ t = Thread.new do
335
+ begin
336
+ server = TCPServer.new 65520
337
+ q.enq :go
338
+ client = server.accept
339
+ Thread.stop
340
+ ensure
341
+ server.close rescue nil
342
+ client.close rescue nil
343
+ end
344
+ end
345
+ q.deq
346
+ c = KJess::Connection.new '127.0.0.1', 65520, :timeout => 0.5
347
+
348
+ lambda { c.write('a' * 10000000) }.must_raise KJess::Socket::Timeout
349
+
350
+ t.run
351
+ t.join
352
+ end
353
+ end
265
354
  end
@@ -6,7 +6,7 @@ module KJess::Spec
6
6
 
7
7
  class << self
8
8
  def version
9
- "2.3.4"
9
+ "2.4.1"
10
10
  end
11
11
 
12
12
  def dir
@@ -18,7 +18,7 @@ module KJess::Spec
18
18
  end
19
19
 
20
20
  def jar
21
- File.join( dir, "kestrel_2.9.1-#{version}.jar" )
21
+ File.join( dir, "kestrel_2.9.2-#{version}.jar" )
22
22
  end
23
23
 
24
24
  def queue_path
@@ -47,7 +47,9 @@ import net.lag.kestrel.config._
47
47
 
48
48
  new KestrelConfig {
49
49
  listenAddress = "0.0.0.0"
50
- memcacheListenPort = 22133
50
+ memcacheListenPort = #{KJess::Spec.memcache_port}
51
+ textListenPort = #{KJess::Spec.text_port}
52
+ thriftListenPort = #{KJess::Spec.thrift_port}
51
53
 
52
54
  queuePath = "#{KJess::Spec::KestrelServer.queue_path}"
53
55
 
@@ -62,7 +64,7 @@ new KestrelConfig {
62
64
  default.maxMemorySize = 128.megabytes
63
65
  default.maxJournalSize = 1.gigabyte
64
66
 
65
- admin.httpPort = 2223
67
+ admin.httpPort = #{KJess::Spec.admin_port}
66
68
 
67
69
  admin.statsNodes = new StatsConfig {
68
70
  reporters = new TimeSeriesCollectorConfig
@@ -80,7 +82,7 @@ _EOC
80
82
  end
81
83
 
82
84
  def get_response( path )
83
- uri = URI.parse( "http://localhost:2223/#{path}" )
85
+ uri = URI.parse( "http://localhost:#{KJess::Spec.admin_port}/#{path}" )
84
86
  resp = Net::HTTP.get_response( uri )
85
87
  JSON.parse( resp.body )
86
88
  end
@@ -100,6 +102,7 @@ _EOC
100
102
  def is_running?
101
103
  return "pong" == ping
102
104
  rescue Exception => e
105
+ #$stderr.puts e
103
106
  false
104
107
  end
105
108
 
@@ -129,9 +132,9 @@ _EOC
129
132
  h = get_response( 'shutdown' )
130
133
  return h['response'] == "ok"
131
134
  rescue => e
135
+ $stderr.puts e
132
136
  false
133
137
  end
134
138
  end
135
-
136
139
  end
137
140
  end
@@ -1,8 +1,15 @@
1
1
  require 'spec_helper'
2
2
 
3
+ module KJess::Spec
4
+ class BadRequest < KJess::Request
5
+ keyword 'BADREQUEST'
6
+ arity 1
7
+ end
8
+ end
9
+
3
10
  describe KJess::ClientError do
4
11
  before do
5
- @client = KJess::Client.new
12
+ @client = KJess::Spec.kjess_client()
6
13
  end
7
14
 
8
15
  after do
@@ -10,7 +17,7 @@ describe KJess::ClientError do
10
17
  end
11
18
 
12
19
  it "raises a client error if we send an invalid command" do
13
- lambda { @client.send_recv( KJess::Request::Status.new ) }.must_raise KJess::ClientError
20
+ lambda { @client.send_recv( KJess::Spec::BadRequest.new ) }.must_raise KJess::ClientError
14
21
  end
15
22
  end
16
23
 
@@ -9,4 +9,4 @@ require 'minitest/autorun'
9
9
  require 'minitest/pride'
10
10
  require 'kjess'
11
11
  require 'utils'
12
-
12
+ require 'thread'
@@ -5,6 +5,26 @@ module KJess
5
5
  File.expand_path( "..", ROOT )
6
6
  end
7
7
 
8
+ def self.memcache_port
9
+ ENV['KJESS_MEMCACHE_PORT'] || 33122
10
+ end
11
+
12
+ def self.thrift_port
13
+ ENV['KJESS_THRIFT_PORT'] || 9992
14
+ end
15
+
16
+ def self.text_port
17
+ ENV['KJESS_TEXT_PORT'] || 9998
18
+ end
19
+
20
+ def self.admin_port
21
+ ENV['KJESS_ADMIN_PORT'] || 9999
22
+ end
23
+
24
+ def self.kjess_client
25
+ KJess::Client.new( :port => memcache_port )
26
+ end
27
+
8
28
  def self.reset_server( client )
9
29
  client.flush_all
10
30
  qlist = client.stats['queues']
@@ -7,10 +7,10 @@ namespace :kestrel do
7
7
  require 'uri'
8
8
  require 'net/http'
9
9
 
10
- url = ::URI.parse("http://robey.github.com/kestrel/download/kestrel-#{KJess::Spec::KestrelServer.erver.version}.zip")
10
+ url = ::URI.parse("http://robey.github.com/kestrel/download/kestrel-#{KJess::Spec::KestrelServer.version}.zip")
11
11
 
12
12
  puts "downloading #{url.to_s} to #{KJess::Spec::KestrelServer.zip} ..."
13
- File.open( KJess::Spec::KestrelServer.erver.zip, "wb+") do |f|
13
+ File.open( KJess::Spec::KestrelServer.zip, "wb+") do |f|
14
14
  res = Net::HTTP.get_response( url )
15
15
  f.write( res.body )
16
16
  end
metadata CHANGED
@@ -1,97 +1,106 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: kjess
3
- version: !ruby/object:Gem::Version
4
- hash: 23
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
5
  prerelease:
6
- segments:
7
- - 1
8
- - 0
9
- - 0
10
- version: 1.0.0
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Jeremy Hinegardner
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2012-10-31 00:00:00 Z
19
- dependencies:
20
- - !ruby/object:Gem::Dependency
12
+ date: 2013-01-09 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
21
15
  name: rake
22
- prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
24
17
  none: false
25
- requirements:
18
+ requirements:
26
19
  - - ~>
27
- - !ruby/object:Gem::Version
28
- hash: 11
29
- segments:
30
- - 0
31
- - 9
32
- - 2
33
- - 2
34
- version: 0.9.2.2
20
+ - !ruby/object:Gem::Version
21
+ version: 10.0.3
35
22
  type: :development
36
- version_requirements: *id001
37
- - !ruby/object:Gem::Dependency
38
- name: minitest
39
23
  prerelease: false
40
- requirement: &id002 !ruby/object:Gem::Requirement
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 10.0.3
30
+ - !ruby/object:Gem::Dependency
31
+ name: minitest
32
+ requirement: !ruby/object:Gem::Requirement
41
33
  none: false
42
- requirements:
34
+ requirements:
43
35
  - - ~>
44
- - !ruby/object:Gem::Version
45
- hash: 11
46
- segments:
47
- - 3
48
- - 3
49
- - 0
50
- version: 3.3.0
36
+ - !ruby/object:Gem::Version
37
+ version: 4.4.0
51
38
  type: :development
52
- version_requirements: *id002
53
- - !ruby/object:Gem::Dependency
54
- name: rdoc
55
39
  prerelease: false
56
- requirement: &id003 !ruby/object:Gem::Requirement
40
+ version_requirements: !ruby/object:Gem::Requirement
57
41
  none: false
58
- requirements:
42
+ requirements:
59
43
  - - ~>
60
- - !ruby/object:Gem::Version
61
- hash: 31
62
- segments:
63
- - 3
64
- - 12
65
- version: "3.12"
44
+ - !ruby/object:Gem::Version
45
+ version: 4.4.0
46
+ - !ruby/object:Gem::Dependency
47
+ name: rdoc
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '3.12'
66
54
  type: :development
67
- version_requirements: *id003
68
- - !ruby/object:Gem::Dependency
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '3.12'
62
+ - !ruby/object:Gem::Dependency
69
63
  name: zip
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: 2.0.2
70
+ type: :development
70
71
  prerelease: false
71
- requirement: &id004 !ruby/object:Gem::Requirement
72
+ version_requirements: !ruby/object:Gem::Requirement
72
73
  none: false
73
- requirements:
74
+ requirements:
74
75
  - - ~>
75
- - !ruby/object:Gem::Version
76
- hash: 11
77
- segments:
78
- - 2
79
- - 0
80
- - 2
76
+ - !ruby/object:Gem::Version
81
77
  version: 2.0.2
78
+ - !ruby/object:Gem::Dependency
79
+ name: json
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 1.7.6
82
86
  type: :development
83
- version_requirements: *id004
84
- description: KJess is a pure ruby Kestrel client that supports Kestrel's Memcache style protocol.
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 1.7.6
94
+ description: KJess is a pure ruby Kestrel client that supports Kestrel's Memcache
95
+ style protocol.
85
96
  email: jeremy@copiousfreetime.org
86
97
  executables: []
87
-
88
98
  extensions: []
89
-
90
- extra_rdoc_files:
99
+ extra_rdoc_files:
91
100
  - HISTORY.rdoc
92
101
  - Manifest.txt
93
102
  - README.rdoc
94
- files:
103
+ files:
95
104
  - CONTRIBUTING.md
96
105
  - HISTORY.rdoc
97
106
  - LICENSE
@@ -130,10 +139,12 @@ files:
130
139
  - lib/kjess/response/reloaded_config.rb
131
140
  - lib/kjess/response/server_error.rb
132
141
  - lib/kjess/response/stats.rb
142
+ - lib/kjess/response/status.rb
133
143
  - lib/kjess/response/stored.rb
134
144
  - lib/kjess/response/unknown.rb
135
145
  - lib/kjess/response/value.rb
136
146
  - lib/kjess/response/version.rb
147
+ - lib/kjess/socket.rb
137
148
  - lib/kjess/stats_cache.rb
138
149
  - spec/client_spec.rb
139
150
  - spec/kestrel_server.rb
@@ -147,41 +158,34 @@ files:
147
158
  - tasks/kestrel.rake
148
159
  homepage: http://github.com/copiousfreetime/kjess
149
160
  licenses: []
150
-
151
161
  post_install_message:
152
- rdoc_options:
162
+ rdoc_options:
153
163
  - --main
154
164
  - README.rdoc
155
165
  - --markup
156
166
  - tomdoc
157
- require_paths:
167
+ require_paths:
158
168
  - lib
159
- required_ruby_version: !ruby/object:Gem::Requirement
169
+ required_ruby_version: !ruby/object:Gem::Requirement
160
170
  none: false
161
- requirements:
162
- - - ">="
163
- - !ruby/object:Gem::Version
164
- hash: 3
165
- segments:
166
- - 0
167
- version: "0"
168
- required_rubygems_version: !ruby/object:Gem::Requirement
171
+ requirements:
172
+ - - ! '>='
173
+ - !ruby/object:Gem::Version
174
+ version: '0'
175
+ required_rubygems_version: !ruby/object:Gem::Requirement
169
176
  none: false
170
- requirements:
171
- - - ">="
172
- - !ruby/object:Gem::Version
173
- hash: 3
174
- segments:
175
- - 0
176
- version: "0"
177
+ requirements:
178
+ - - ! '>='
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
177
181
  requirements: []
178
-
179
182
  rubyforge_project:
180
183
  rubygems_version: 1.8.24
181
184
  signing_key:
182
185
  specification_version: 3
183
- summary: KJess is a pure ruby Kestrel client that supports Kestrel's Memcache style protocol.
184
- test_files:
186
+ summary: KJess is a pure ruby Kestrel client that supports Kestrel's Memcache style
187
+ protocol.
188
+ test_files:
185
189
  - spec/client_spec.rb
186
190
  - spec/kestrel_server.rb
187
191
  - spec/request/set_spec.rb