Skip to content
Browse files

v2.0

  • Loading branch information...
0 parents commit 252358910077578023f9c805f2ad60e974e38224 @freels committed
1 .gitignore
@@ -0,0 +1 @@
+pkg/*
28 Rakefile
@@ -0,0 +1,28 @@
+ROOT_DIR = File.expand_path(File.dirname(__FILE__))
+
+require 'rubygems' rescue nil
+require 'rake'
+require 'spec/rake/spectask'
+
+task :default => :spec
+
+desc "Run all specs in spec directory."
+Spec::Rake::SpecTask.new(:spec) do |t|
+ t.spec_opts = ['--options', "\"#{ROOT_DIR}/spec/spec.opts\""]
+ t.spec_files = FileList['spec/**/*_spec.rb']
+end
+
+# gemification with jeweler
+begin
+ require 'jeweler'
+ Jeweler::Tasks.new do |gemspec|
+ gemspec.name = "kestrel-client"
+ gemspec.summary = "Ruby Kestrel client"
+ gemspec.description = "Ruby client for the Kestrel queue server"
+ gemspec.email = "rael@twitter.com"
+ gemspec.homepage = "http://github.com/freels/kestrel-client"
+ gemspec.authors = ["Matt Freels", "Rael Dornfest"]
+ end
+rescue LoadError
+ puts "Jeweler not available. Install it with: gem install jeweler"
+end
1 VERSION
@@ -0,0 +1 @@
+0.2.0
64 kestrel-client.gemspec
@@ -0,0 +1,64 @@
+# Generated by jeweler
+# DO NOT EDIT THIS FILE DIRECTLY
+# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
+# -*- encoding: utf-8 -*-
+
+Gem::Specification.new do |s|
+ s.name = %q{kestrel-client}
+ s.version = "0.2.0"
+
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
+ s.authors = ["Matt Freels", "Rael Dornfest"]
+ s.date = %q{2010-02-11}
+ s.description = %q{Ruby client for the Kestrel queue server}
+ s.email = %q{rael@twitter.com}
+ s.files = [
+ ".gitignore",
+ "Rakefile",
+ "VERSION",
+ "kestrel-client.gemspec",
+ "lib/kestrel-client.rb",
+ "lib/kestrel.rb",
+ "lib/kestrel/client.rb",
+ "lib/kestrel/client/blocking.rb",
+ "lib/kestrel/client/envelope.rb",
+ "lib/kestrel/client/json.rb",
+ "lib/kestrel/client/proxy.rb",
+ "lib/kestrel/client/unmarshal.rb",
+ "lib/kestrel/config.rb",
+ "spec/kestrel/client/blocking_spec.rb",
+ "spec/kestrel/client/envelope_spec.rb",
+ "spec/kestrel/client/json_spec.rb",
+ "spec/kestrel/client/unmarshal_spec.rb",
+ "spec/kestrel/client_spec.rb",
+ "spec/kestrel/config/kestrel.yml",
+ "spec/kestrel/config_spec.rb",
+ "spec/spec.opts",
+ "spec/spec_helper.rb"
+ ]
+ s.homepage = %q{http://github.com/freels/kestrel-client}
+ s.rdoc_options = ["--charset=UTF-8"]
+ s.require_paths = ["lib"]
+ s.rubygems_version = %q{1.3.5}
+ s.summary = %q{Ruby Kestrel client}
+ s.test_files = [
+ "spec/kestrel/client/blocking_spec.rb",
+ "spec/kestrel/client/envelope_spec.rb",
+ "spec/kestrel/client/json_spec.rb",
+ "spec/kestrel/client/unmarshal_spec.rb",
+ "spec/kestrel/client_spec.rb",
+ "spec/kestrel/config_spec.rb",
+ "spec/spec_helper.rb"
+ ]
+
+ if s.respond_to? :specification_version then
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
+ s.specification_version = 3
+
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
+ else
+ end
+ else
+ end
+end
+
1 lib/kestrel-client.rb
@@ -0,0 +1 @@
+require 'kestrel'
10 lib/kestrel.rb
@@ -0,0 +1,10 @@
+require 'yaml'
+require 'socket'
+require 'memcached'
+
+require 'kestrel/config'
+require 'kestrel/client'
+require 'kestrel/client/proxy'
+require 'kestrel/client/envelope'
+require 'kestrel/client/blocking'
+require 'kestrel/client/unmarshal'
99 lib/kestrel/client.rb
@@ -0,0 +1,99 @@
+module Kestrel
+ class Client < Memcached::Rails
+ class Timeout < Timeout::Error; end
+
+ QUEUE_STAT_NAMES = %w{items bytes total_items logsize expired_items mem_items mem_bytes age discarded}
+
+ def flush(queue)
+ count = 0
+ while sizeof(queue) > 0
+ while get(queue, true)
+ count += 1
+ end
+ end
+ count
+ end
+
+ def peek(queue)
+ val = get(queue)
+ set(queue, val)
+ val
+ end
+
+ def sizeof(queue)
+ stat_info = stat(queue)
+ stat_info ? stat_info['items'] : 0
+ end
+
+ def available_queues
+ stats['queues'].keys.sort
+ end
+
+ def stats
+ merge_stats(servers.map { |server| stats_for_server(server) })
+ end
+
+ def stat(queue)
+ stats['queues'][queue]
+ end
+
+ private
+
+ def stats_for_server(server)
+ server_name, port = server.split(/:/)
+ socket = TCPSocket.new(server_name, port)
+ socket.puts "STATS"
+
+ stats = Hash.new
+ stats['queues'] = Hash.new
+ while line = socket.readline
+ if line =~ /^STAT queue_(\S+?)_(#{QUEUE_STAT_NAMES.join("|")}) (\S+)/
+ queue_name, queue_stat_name, queue_stat_value = $1, $2, deserialize_stat_value($3)
+ stats['queues'][queue_name] ||= Hash.new
+ stats['queues'][queue_name][queue_stat_name] = queue_stat_value
+ elsif line =~ /^STAT (\w+) (\S+)/
+ stat_name, stat_value = $1, deserialize_stat_value($2)
+ stats[stat_name] = stat_value
+ elsif line =~ /^END/
+ socket.close
+ break
+ elsif defined?(RAILS_DEFAULT_LOGGER)
+ RAILS_DEFAULT_LOGGER.debug("KestrelClient#stats_for_server: Ignoring #{line}")
+ end
+ end
+
+ stats
+ end
+
+ def merge_stats(all_stats)
+ result = Hash.new
+
+ all_stats.each do |stats|
+ stats.each do |stat_name, stat_value|
+ if result.has_key?(stat_name)
+ if stat_value.kind_of?(Hash)
+ result[stat_name] = merge_stats([result[stat_name], stat_value])
+ else
+ result[stat_name] += stat_value
+ end
+ else
+ result[stat_name] = stat_value
+ end
+ end
+ end
+
+ result
+ end
+
+ def deserialize_stat_value(value)
+ case value
+ when /^\d+\.\d+$/:
+ value.to_f
+ when /^\d+$/:
+ value.to_i
+ else
+ value
+ end
+ end
+ end
+end
31 lib/kestrel/client/blocking.rb
@@ -0,0 +1,31 @@
+module Kestrel
+ class Client
+ class Blocking < Proxy
+ DEFAULT_EXPIRY = 0
+ WAIT_TIME_BEFORE_RETRY = 0.25
+
+ def get(*args)
+ while !(response = client.get(*args))
+ sleep WAIT_TIME_BEFORE_RETRY
+ end
+ response
+ end
+
+ def get_without_blocking(*args)
+ client.get(*args)
+ end
+
+ def set(key, value, expiry = DEFAULT_EXPIRY, raw = false)
+ @retried = false
+ begin
+ client.set(key, value, expiry, raw)
+ rescue Memcached::Error => e
+ raise if @retried
+ sleep(WAIT_TIME_BEFORE_RETRY)
+ @retried = true
+ retry
+ end
+ end
+ end
+ end
+end
25 lib/kestrel/client/envelope.rb
@@ -0,0 +1,25 @@
+module Kestrel
+ class Client
+ class Envelope < Proxy
+ attr_accessor :envelope_class
+
+ def initialize(envelope_class, client)
+ @envelope_class = envelope_class
+ super(client)
+ end
+
+ def get(*args)
+ response = client.get(*args)
+ if response.respond_to?(:unwrap)
+ response.unwrap
+ else
+ response
+ end
+ end
+
+ def set(key, value, *args)
+ client.set(key, envelope_class.new(value), *args)
+ end
+ end
+ end
+end
16 lib/kestrel/client/json.rb
@@ -0,0 +1,16 @@
+require 'json'
+
+module Kestrel
+ class Client
+ class Json < Proxy
+ def get(*args)
+ response = client.get(*args)
+ if response.is_a?(String)
+ HashWithIndifferentAccess.new(JSON.parse(response)) rescue response
+ else
+ response
+ end
+ end
+ end
+ end
+end
15 lib/kestrel/client/proxy.rb
@@ -0,0 +1,15 @@
+module Kestrel
+ class Client
+ class Proxy
+ attr_reader :client
+
+ def initialize(client)
+ @client = client
+ end
+
+ def method_missing(method, *args, &block)
+ client.send(method, *args, &block)
+ end
+ end
+ end
+end
21 lib/kestrel/client/unmarshal.rb
@@ -0,0 +1,21 @@
+module Kestrel
+ class Client
+ class Unmarshal < Proxy
+ def get(keys, raw = false)
+ response = client.get(keys, true)
+ return response if raw
+ if is_marshaled?(response)
+ Marshal.load_with_constantize(response, loaded_constants = [])
+ else
+ response
+ end
+ end
+
+ def is_marshaled?(object)
+ object.to_s[0] == Marshal::MAJOR_VERSION && object.to_s[1] == Marshal::MINOR_VERSION
+ rescue Exception
+ false
+ end
+ end
+ end
+end
48 lib/kestrel/config.rb
@@ -0,0 +1,48 @@
+require 'yaml'
+
+module Kestrel
+ module Config
+ class ConfigNotLoaded < StandardError; end
+
+ extend self
+
+ attr_accessor :environment, :config
+
+ def load(config_file)
+ self.config = YAML.load_file(config_file)
+ end
+
+ def environment
+ @environment ||= 'development'
+ end
+
+ def config
+ @config or raise ConfigNotLoaded
+ end
+
+ def namespace(namespace)
+ client_args_from config[namespace.to_s][environment.to_s]
+ end
+
+ def default
+ client_args_from config[environment.to_s]
+ end
+
+ def new_client(space = nil)
+ Client.new *(space ? namespace(space) : default)
+ end
+
+ alias method_missing namespace
+
+ private
+
+ def client_args_from(config)
+ sanitized = config.inject({}) do |sanitized, (key, val)|
+ sanitized[key.to_sym] = val; sanitized
+ end
+ servers = sanitized.delete(:servers)
+
+ [servers, sanitized]
+ end
+ end
+end
72 spec/kestrel/client/blocking_spec.rb
@@ -0,0 +1,72 @@
+require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
+
+describe "Kestrel::Client::Blocking" do
+ describe "Instance Methods" do
+ before do
+ Kestrel::Config.load TEST_CONFIG_FILE
+ @raw_kestrel_client = Kestrel::Client.new(*Kestrel::Config.default)
+ @kestrel = Kestrel::Client::Blocking.new(@raw_kestrel_client)
+ end
+
+ describe "#set" do
+ before do
+ @queue = "some_queue"
+ @value = "some_value"
+ end
+
+ it "blocks on a set until the set works" do
+ @queue = "some_queue"
+ @value = "some_value"
+ mock(@raw_kestrel_client)\
+ .set(@queue, @value, anything, anything) { raise Memcached::Error }.then\
+ .set(@queue, @value, anything, anything) { :mcguffin }
+ mock(@kestrel).sleep(Kestrel::Client::Blocking::WAIT_TIME_BEFORE_RETRY).once
+ @kestrel.set(@queue, @value).should == :mcguffin
+ end
+
+ it "raises if two sets in a row fail" do
+ mock(@raw_kestrel_client)\
+ .set(@queue, @value, anything, anything) { raise Memcached::Error }.then\
+ .set(@queue, @value, anything, anything) { raise Memcached::Error }
+ mock(@kestrel).sleep(Kestrel::Client::Blocking::WAIT_TIME_BEFORE_RETRY).once
+ lambda { @kestrel.set(@queue, @value) }.should raise_error(Memcached::Error)
+ end
+
+ it "passes along the default expiry time if none is given" do
+ @queue = "some_queue"
+ @value = "some_value"
+ mock(@raw_kestrel_client).set(@queue, @value, Kestrel::Client::Blocking::DEFAULT_EXPIRY, anything)
+ @kestrel.set(@queue, @value)
+ end
+
+ it "passes along the given expiry time if one is passed in" do
+ @queue = "some_queue"
+ @value = "some_value"
+ mock(@raw_kestrel_client).set(@queue, @value, 60, anything)
+ @kestrel.set(@queue, @value, 60)
+ end
+ end
+
+ describe "#get" do
+ before do
+ @queue = "some_queue"
+ end
+
+ it "blocks on a get until the get works" do
+ mock(@raw_kestrel_client)\
+ .get(@queue) { nil }.then\
+ .get(@queue) { :mcguffin }
+ mock(@kestrel).sleep(Kestrel::Client::Blocking::WAIT_TIME_BEFORE_RETRY).once
+ @kestrel.get(@queue).should == :mcguffin
+ end
+
+ describe "#get_without_blocking" do
+ it "does not block" do
+ mock(@raw_kestrel_client).get(@queue) { nil }
+ mock(@kestrel).sleep(Kestrel::Client::Blocking::WAIT_TIME_BEFORE_RETRY).never
+ @kestrel.get_without_blocking(@queue).should be_nil
+ end
+ end
+ end
+ end
+end
34 spec/kestrel/client/envelope_spec.rb
@@ -0,0 +1,34 @@
+require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
+
+class Envelope; end
+
+describe Kestrel::Client::Envelope do
+ describe "Instance Methods" do
+ before do
+ Kestrel::Config.load TEST_CONFIG_FILE
+ @raw_kestrel_client = Kestrel::Client.new(*Kestrel::Config.default)
+ @kestrel = Kestrel::Client::Envelope.new(Envelope, @raw_kestrel_client)
+ end
+
+ describe "#get and #set" do
+ describe "envelopes" do
+ it "creates an envelope on a set" do
+ mock(Envelope).new(:mcguffin)
+ @kestrel.set('a_queue', :mcguffin)
+ end
+
+ it "unwraps an envelope on a get" do
+ envelope = Envelope.new
+ mock(@raw_kestrel_client).get('a_queue') { envelope }
+ mock(envelope).unwrap { :mcguffin }
+ @kestrel.get('a_queue').should == :mcguffin
+ end
+
+ it "does not unwrap a nil get" do
+ mock(@raw_kestrel_client).get('a_queue') { nil }
+ @kestrel.get('a_queue').should be_nil
+ end
+ end
+ end
+ end
+end
42 spec/kestrel/client/json_spec.rb
@@ -0,0 +1,42 @@
+require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
+
+require 'kestrel/client/json'
+
+describe Kestrel::Client::Json do
+ describe "Instance Methods" do
+ before do
+ Kestrel::Config.load TEST_CONFIG_FILE
+ @raw_kestrel_client = Kestrel::Client.new(*Kestrel::Config.default)
+ @kestrel = Kestrel::Client::Json.new(@raw_kestrel_client)
+ end
+
+ describe "#get" do
+ it "parses json" do
+ mock(@raw_kestrel_client).get('a_queue') { '{"a": 1, "b": [{"c": 2}]}' }
+ @kestrel.get('a_queue').should == {"a" => 1, "b" => ["c" => 2]}
+ end
+
+ it "uses a HashWithIndifferentAccess" do
+ mock(@raw_kestrel_client).get('a_queue') { '{"a": 1, "b": [{"c": 2}]}' }
+ @kestrel.get('a_queue').class.should == HashWithIndifferentAccess
+ end
+
+ it "passes through non-strings" do
+ mock(@raw_kestrel_client).get('a_queue') { {:key => "value"} }
+ @kestrel.get('a_queue').should == {:key => "value"}
+ end
+
+ it "passes through strings that are not json" do
+ mock(@raw_kestrel_client).get('a_queue') { "I am not JSON" }
+ @kestrel.get('a_queue').should == "I am not JSON"
+ end
+ end
+ end
+end
+
+class HashWithIndifferentAccess < Hash
+ def initialize(hash = {})
+ super()
+ merge!(hash)
+ end
+end
61 spec/kestrel/client/unmarshal_spec.rb
@@ -0,0 +1,61 @@
+require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
+
+describe Kestrel::Client::Unmarshal do
+ describe "Instance Methods" do
+ before do
+ Kestrel::Config.load TEST_CONFIG_FILE
+ @raw_kestrel_client = Kestrel::Client.new(*Kestrel::Config.default)
+ @kestrel = Kestrel::Client::Unmarshal.new(@raw_kestrel_client)
+ end
+
+ describe "#get" do
+ it "unmarshals marshaled objects" do
+ test_object = {:a => 1, :b => [1, 2, 3]}
+ mock(@raw_kestrel_client).get('a_queue', true) { Marshal.dump(test_object) }
+ @kestrel.get('a_queue').should == test_object
+ end
+
+ it "does not unmarshal when raw is true" do
+ test_object = {:a => 1, :b => [1, 2, 3]}
+ mock(@raw_kestrel_client).get('a_queue', true) { Marshal.dump(test_object) }
+ @kestrel.get('a_queue', true).should == Marshal.dump(test_object)
+ end
+
+ it "pasess through objects" do
+ test_object = Object.new
+ mock(@raw_kestrel_client).get('a_queue', true) { test_object }
+ @kestrel.get('a_queue').should == test_object
+ end
+
+ it "passes through strings" do
+ mock(@raw_kestrel_client).get('a_queue', true) { "I am not marshaled" }
+ @kestrel.get('a_queue').should == "I am not marshaled"
+ end
+ end
+
+ describe "#isMarshaled" do
+ it "should foo" do
+ @kestrel.is_marshaled?("foo").should be_false
+ @kestrel.is_marshaled?(Marshal.dump("foo")).should be_true
+
+ @kestrel.is_marshaled?({}).should be_false
+ @kestrel.is_marshaled?(Marshal.dump({})).should be_true
+
+ @kestrel.is_marshaled?(BadObject.new).should be_false
+ @kestrel.is_marshaled?(Marshal.dump(BadObject.new)).should be_true
+ end
+ end
+ end
+end
+
+class BadObject
+ def to_s
+ raise Exception
+ end
+end
+
+module Marshal
+ def self.load_with_constantize(source, loaded_constants = [])
+ self.load(source)
+ end
+end
107 spec/kestrel/client_spec.rb
@@ -0,0 +1,107 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe Kestrel::Client do
+ describe "Instance Methods" do
+ before do
+ Kestrel::Config.load TEST_CONFIG_FILE
+ @kestrel = Kestrel::Client.new(*Kestrel::Config.default)
+ stub(@kestrel).with_timing(anything) { |_, block| block.call }
+ end
+
+ describe "#get and #set" do
+ it "basic operation" do
+ @kestrel.flush(queue = "test_queue")
+ @kestrel.set(queue, value = "russell's reserve")
+ @kestrel.get(queue).should == value
+ end
+ end
+
+ describe "#flush" do
+ before do
+ @queue = "some_random_queue_#{Time.now.to_i}_#{rand(10000)}"
+ end
+
+ it "counts the number of items flushed and passes each of them to a given block" do
+ %w{A B C}.each { |item| @kestrel.set(@queue, item) }
+ @kestrel.flush(@queue).should == 3
+ end
+
+ it "does not attempt to Marshal load the data being flushed" do
+ @kestrel.set(@queue, "some_stuff", 0, true)
+ mock(Marshal).load.never
+ @kestrel.flush(@queue).should == 1
+ end
+ end
+
+ describe "#peek" do
+ it "should return first item from the queue and reenqueue" do
+ @queue = "some_random_queue_#{Time.now.to_i}_#{rand(10000)}"
+ @kestrel.set(@queue, "item_1")
+ @kestrel.set(@queue, "item_2")
+ @kestrel.peek(@queue).should == "item_1"
+ @kestrel.sizeof(@queue).should == 2
+ end
+ end
+
+ describe "#stats" do
+ it "retrieves stats" do
+ stats = @kestrel.stats
+ %w{uptime time version curr_items total_items bytes curr_connections total_connections
+ cmd_get cmd_set get_hits get_misses bytes_read bytes_written queues}.each do |stat|
+ stats[stat].should_not be_nil
+ end
+
+ @kestrel.set("test-queue-name", 97)
+ stats['queues']["test-queue-name"].should_not be_nil
+ Kestrel::Client::QUEUE_STAT_NAMES.each do |queue_stat|
+ stats['queues']['test-queue-name'][queue_stat].should_not be_nil
+ end
+ end
+
+ it "merge in stats from all the servers" do
+ server = @kestrel.servers.first
+ stub(@kestrel).servers { [server] }
+ stats_for_one_server = @kestrel.stats
+
+ server = @kestrel.servers.first
+ stub(@kestrel).servers { [server] * 2 }
+ stats_for_two_servers = @kestrel.stats
+
+ stats_for_two_servers['bytes'].should == 2*stats_for_one_server['bytes']
+ end
+ end
+
+ describe "#stat" do
+ it "get stats for single queue" do
+ @kestrel.set(queue = "test-queue-name", 97)
+ all_stats = @kestrel.stats
+ @kestrel.stat(queue).should == all_stats['queues'][queue]
+ end
+ end
+
+ describe "#sizeof" do
+ before do
+ @queue = "some_random_queue_#{Time.now.to_i}_#{rand(10000)}"
+ end
+
+ it "reports the size of the queue" do
+ 100.times { @kestrel.set(@queue, true) }
+ @kestrel.sizeof(@queue).should == 100
+ end
+
+ it "reports the size of a non-existant queue as 0" do
+ queue = "some_random_queue_#{Time.now.to_i}_#{rand(10000)}"
+ @kestrel.sizeof(queue).should == 0
+ end
+ end
+
+ describe "#available_queues" do
+ it "returns all the queue names" do
+ @kestrel.set("test-queue-name1", 97)
+ @kestrel.set("test-queue-name2", 155)
+ @kestrel.available_queues.should include('test-queue-name1')
+ @kestrel.available_queues.should include('test-queue-name2')
+ end
+ end
+ end
+end
42 spec/kestrel/config/kestrel.yml
@@ -0,0 +1,42 @@
+defaults: &defaults
+ distribution: :random
+ timeout: 10
+ connect_timeout: 2
+ server_failure_limit: 10
+ auto_eject_hosts: false
+
+production: &production
+ <<: *defaults
+ auto_eject_hosts: true
+ server_failure_limit: 4
+ servers:
+ - localhost:22133
+
+staging:
+ <<: *production
+ servers:
+ - localhost:22133
+
+development: &development
+ <<: *defaults
+ show_backtraces: true
+ servers:
+ - localhost:22133
+
+benchmark:
+ <<: *development
+
+test:
+ <<: *development
+
+replication_lag:
+ <<: *development
+
+selenium:
+ <<: *development
+
+foo_space:
+ production:
+ <<: *production
+ development:
+ <<: *development
75 spec/kestrel/config_spec.rb
@@ -0,0 +1,75 @@
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe Kestrel::Config do
+ before do
+ Kestrel::Config.environment = nil
+ Kestrel::Config.load TEST_CONFIG_FILE
+
+ # to sniff namespace foo_space
+ Kestrel::Config.config['foo_space']['development']['connect_timeout'] = 8
+ end
+
+ describe "load" do
+ it "loads a yaml file" do
+ Kestrel::Config.config = nil
+ lambda { Kestrel::Config.default }.should raise_error(Kestrel::Config::ConfigNotLoaded)
+
+ Kestrel::Config.load TEST_CONFIG_FILE
+ lambda { Kestrel::Config.default }.should_not raise_error(Kestrel::Config::ConfigNotLoaded)
+ end
+ end
+
+ shared_examples_for "config getters" do
+ it "returns a tuple of [servers, options]" do
+ config = @configurer.call
+ config.should be_a(Array)
+
+ [String, Array].should include(config.first.class)
+ config.last.should be_a(Hash)
+
+ config.last.keys.map{|k| k.class }.uniq.should == [Symbol]
+ end
+
+ it "defaults to development enviroment" do
+ @configurer.call.last[:server_failure_limit].should == 10 # development options should pull 10 from defaults
+ end
+
+ it "returns config for the specified environment" do
+ Kestrel::Config.environment = :production
+ @configurer.call.last[:server_failure_limit].should == 4 # production is set to 4
+ end
+ end
+
+ describe "namespace" do
+ before { @configurer = lambda { Kestrel::Config.namespace(:foo_space) } }
+
+ it_should_behave_like "config getters"
+
+ it "returns args for Kestrel::Client.new for the appropriate namespace" do
+ Kestrel::Config.foo_space.last[:connect_timeout].should == 8
+ end
+
+ it "is aliased to method_missing" do
+ Kestrel::Config.foo_space.should == Kestrel::Config.namespace(:foo_space)
+ end
+ end
+
+ describe "default" do
+ before { @configurer = lambda { Kestrel::Config.default } }
+
+ it_should_behave_like "config getters"
+ end
+
+ describe "new_client" do
+ it "returns a Kestrel::Client instance" do
+ client = Kestrel::Config.new_client
+ client.should be_a(Kestrel::Client)
+ end
+
+ it "can take a namespace" do
+ client = Kestrel::Config.new_client(:foo_space)
+ client.should be_a(Kestrel::Client)
+ client.options[:connect_timeout].should == 8
+ end
+ end
+end
7 spec/spec.opts
@@ -0,0 +1,7 @@
+--colour
+--format progress
+--loadby mtime
+--reverse
+--timeout 20
+--diff
+--backtrace
15 spec/spec_helper.rb
@@ -0,0 +1,15 @@
+require 'rubygems'
+require 'spec'
+
+spec_dir = File.dirname(__FILE__)
+
+# make sure we load local libs rather than gems first
+$: << File.expand_path("#{spec_dir}/../lib")
+
+require 'kestrel'
+
+TEST_CONFIG_FILE = File.expand_path("#{spec_dir}/kestrel/config/kestrel.yml")
+
+Spec::Runner.configure do |config|
+ config.mock_with :rr
+end

0 comments on commit 2523589

Please sign in to comment.
Something went wrong with that request. Please try again.