fairway 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,75 @@
1
+ require "spec_helper"
2
+
3
+ module Fairway
4
+ describe ChanneledConnection do
5
+ let(:config) do
6
+ Config.new do |c|
7
+ c.facet { |message| message[:facet] }
8
+ end
9
+ end
10
+ let(:connection) do
11
+ ChanneledConnection.new(Connection.new(config)) do |message|
12
+ message[:topic]
13
+ end
14
+ end
15
+ let(:redis) { config.redis }
16
+ let(:message) { { facet: 1, topic: "event:helloworld" } }
17
+
18
+ describe "#deliver" do
19
+ context "multiple queues exist for message type" do
20
+ it "adds message for both queues" do
21
+ config.register_queue("myqueue", ".*:helloworld")
22
+ config.register_queue("yourqueue", "event:.*world")
23
+ connection.deliver(message)
24
+ redis.llen("myqueue:1").should == 1
25
+ redis.llen("yourqueue:1").should == 1
26
+ end
27
+ end
28
+
29
+ context "registered queue exists for message type" do
30
+ before do
31
+ config.register_queue("myqueue", "event:helloworld")
32
+ end
33
+
34
+ it "adds message to the environment facet for the queue" do
35
+ connection.deliver(message)
36
+ redis.llen("myqueue:1").should == 1
37
+ redis.lindex("myqueue:1", 0).should == message.to_json
38
+ end
39
+
40
+ it "adds facet to list of active facets" do
41
+ connection.deliver(message)
42
+ redis.smembers("myqueue:active_facets").should == ["1"]
43
+ end
44
+
45
+ it "pushes facet onto facet queue" do
46
+ connection.deliver(message)
47
+ redis.llen("myqueue:facet_queue").should == 1
48
+ redis.lindex("myqueue:facet_queue", 0).should == "1"
49
+ end
50
+
51
+ it "doesn't push onto to facet queue if currently active" do
52
+ redis.sadd("myqueue:active_facets", "1")
53
+ connection.deliver(message)
54
+ redis.llen("myqueue:facet_queue").should == 0
55
+ end
56
+ end
57
+
58
+ context "registered queue exists for another message type" do
59
+ before do
60
+ config.register_queue("myqueue", "foo")
61
+ end
62
+
63
+ it "doesn't add message to the queue" do
64
+ connection.deliver(message)
65
+ redis.llen("myqueue:1").should == 0
66
+ end
67
+
68
+ it "doesn't add facet to list of active facets" do
69
+ connection.deliver(message)
70
+ redis.smembers("myqueue:active_facets").should == []
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,55 @@
1
+ require "spec_helper"
2
+
3
+ module Fairway
4
+ describe Config do
5
+ describe "#initialize" do
6
+ it "yields itself to a block" do
7
+ config = Config.new do |c|
8
+ c.namespace = "x"
9
+ end
10
+ config.namespace.should == "x"
11
+ end
12
+ end
13
+
14
+ describe "#facet" do
15
+ context "when called with a block" do
16
+ it "sets the facet" do
17
+ config = Config.new
18
+ config.facet do |message|
19
+ "foo"
20
+ end
21
+ config.facet.call({}).should == "foo"
22
+ end
23
+ end
24
+ end
25
+
26
+ it "allows setting of redis connection options" do
27
+ Config.new do |config|
28
+ config.redis = { host: "127.0.0.1", port: 6379 }
29
+ end
30
+ end
31
+
32
+ it "allows setting of redis namespace" do
33
+ config = Config.new do |config|
34
+ config.namespace = "ns"
35
+ end
36
+
37
+ config.namespace.should == "ns"
38
+ end
39
+
40
+ it "sets the default facet" do
41
+ config = Config.new
42
+ config.facet.call(environment_id: 5, facet: 1).should == Config::DEFAULT_FACET
43
+ end
44
+
45
+ it "allows custom faceting" do
46
+ config = Config.new do |config|
47
+ config.facet do |message|
48
+ message[:environment_id]
49
+ end
50
+ end
51
+
52
+ config.facet.call(environment_id: 5, facet: 1).should == 5
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,79 @@
1
+ require "spec_helper"
2
+
3
+ module Fairway
4
+ describe Connection do
5
+ let(:config) do
6
+ Config.new do |c|
7
+ c.facet { |message| message[:facet] }
8
+ end
9
+ end
10
+
11
+ let(:connection) { Connection.new(config) }
12
+ let(:redis) { config.redis }
13
+ let(:message) { { facet: 1, topic: "event:helloworld" } }
14
+
15
+ describe "#initialize" do
16
+ it "registers queues from the config" do
17
+ config = Config.new
18
+ config.register_queue("myqueue", ".*")
19
+ config.redis.hgetall("registered_queues").should == {}
20
+ Connection.new(config)
21
+
22
+ config.redis.hgetall("registered_queues").should == {
23
+ "myqueue" => ".*"
24
+ }
25
+ end
26
+
27
+ context "when an existing queue definition does not match" do
28
+ it "raises a QueueMismatchError"
29
+ end
30
+ end
31
+
32
+ describe "#deliver" do
33
+ it "publishes message over the message topic channel" do
34
+ redis = Redis.new
35
+
36
+ redis.psubscribe("*") do |on|
37
+ on.psubscribe do |pattern, total|
38
+ connection.deliver(message)
39
+ end
40
+
41
+ on.pmessage do |pattern, channel, received_message|
42
+ received_message.should == message.to_json
43
+ channel.should == "default"
44
+ redis.punsubscribe(pattern)
45
+ end
46
+ end
47
+ end
48
+
49
+ context "registered queue exists for message type" do
50
+ before do
51
+ config.register_queue("myqueue")
52
+ end
53
+
54
+ it "adds message to the environment facet for the queue" do
55
+ connection.deliver(message)
56
+ redis.llen("myqueue:1").should == 1
57
+ redis.lindex("myqueue:1", 0).should == message.to_json
58
+ end
59
+
60
+ it "adds facet to list of active facets" do
61
+ connection.deliver(message)
62
+ redis.smembers("myqueue:active_facets").should == ["1"]
63
+ end
64
+
65
+ it "pushes facet onto facet queue" do
66
+ connection.deliver(message)
67
+ redis.llen("myqueue:facet_queue").should == 1
68
+ redis.lindex("myqueue:facet_queue", 0).should == "1"
69
+ end
70
+
71
+ it "doesn't push onto to facet queue if currently active" do
72
+ redis.sadd("myqueue:active_facets", "1")
73
+ connection.deliver(message)
74
+ redis.llen("myqueue:facet_queue").should == 0
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,101 @@
1
+ require "spec_helper"
2
+
3
+ module Fairway
4
+ describe QueueReader do
5
+ let(:config) do
6
+ Config.new do |c|
7
+ c.facet { |message| message[:facet] }
8
+ end
9
+ end
10
+ let(:connection) do
11
+ c = Connection.new(config)
12
+ ChanneledConnection.new(c) do |message|
13
+ message[:topic]
14
+ end
15
+ end
16
+ let(:message) { { facet: 1, topic: "event:helloworld" } }
17
+
18
+ describe "#initialize" do
19
+ it "requires a Connection and queue names" do
20
+ lambda { QueueReader.new }.should raise_error(ArgumentError)
21
+ end
22
+ end
23
+
24
+ describe "#pull" do
25
+ before do
26
+ config.register_queue("myqueue", "event:helloworld")
27
+ end
28
+
29
+ it "pulls a message off the queue using FIFO strategy" do
30
+ connection.deliver(message1 = message.merge(message: 1))
31
+ connection.deliver(message2 = message.merge(message: 2))
32
+
33
+ reader = QueueReader.new(connection, "myqueue")
34
+ reader.pull.should == ["myqueue", message1.to_json]
35
+ reader.pull.should == ["myqueue", message2.to_json]
36
+ end
37
+
38
+ it "pulls from facets of the queue in a round-robin nature" do
39
+ connection.deliver(message1 = message.merge(facet: 1, message: 1))
40
+ connection.deliver(message2 = message.merge(facet: 1, message: 2))
41
+ connection.deliver(message3 = message.merge(facet: 2, message: 3))
42
+
43
+ reader = QueueReader.new(connection, "myqueue")
44
+ reader.pull.should == ["myqueue", message1.to_json]
45
+ reader.pull.should == ["myqueue", message3.to_json]
46
+ reader.pull.should == ["myqueue", message2.to_json]
47
+ end
48
+
49
+ it "removes facet from active list if it becomes empty" do
50
+ connection.deliver(message)
51
+
52
+ config.redis.smembers("myqueue:active_facets").should == ["1"]
53
+ reader = QueueReader.new(connection, "myqueue")
54
+ reader.pull
55
+ config.redis.smembers("myqueue:active_facets").should be_empty
56
+ end
57
+
58
+ it "returns nil if there are no messages to retrieve" do
59
+ connection.deliver(message)
60
+
61
+ reader = QueueReader.new(connection, "myqueue")
62
+ reader.pull.should == ["myqueue", message.to_json]
63
+ reader.pull.should be_nil
64
+ end
65
+
66
+ context "pulling from multiple queues" do
67
+ before do
68
+ config.register_queue("myqueue1", "event:1")
69
+ config.register_queue("myqueue2", "event:2")
70
+ end
71
+
72
+ it "pulls messages off first queue with a message" do
73
+ connection.deliver(message1 = message.merge(topic: "event:1"))
74
+ connection.deliver(message2 = message.merge(topic: "event:2"))
75
+
76
+ reader = QueueReader.new(connection, "myqueue2", "myqueue1")
77
+ reader.pull.should == ["myqueue2", message2.to_json]
78
+ reader.pull.should == ["myqueue1", message1.to_json]
79
+ end
80
+
81
+ it "returns nil if no queues have messages" do
82
+ reader = QueueReader.new(connection, "myqueue2", "myqueue1")
83
+ reader.pull.should be_nil
84
+ end
85
+
86
+ it "pulls from facets of the queue in a round-robin nature" do
87
+ connection.deliver(message1 = message.merge(facet: 1, topic: "event:1"))
88
+ connection.deliver(message2 = message.merge(facet: 1, topic: "event:1"))
89
+ connection.deliver(message3 = message.merge(facet: 2, topic: "event:1"))
90
+ connection.deliver(message4 = message.merge(facet: 1, topic: "event:2"))
91
+
92
+ reader = QueueReader.new(connection, "myqueue2", "myqueue1")
93
+ reader.pull.should == ["myqueue2", message4.to_json]
94
+ reader.pull.should == ["myqueue1", message1.to_json]
95
+ reader.pull.should == ["myqueue1", message3.to_json]
96
+ reader.pull.should == ["myqueue1", message2.to_json]
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,28 @@
1
+ require "spec_helper"
2
+
3
+ module Fairway
4
+ describe Scripts do
5
+ describe "#initialize" do
6
+ it "requires a redis client" do
7
+ lambda {
8
+ Scripts.new
9
+ }.should raise_error(ArgumentError)
10
+ end
11
+ end
12
+
13
+ describe "#method_missing" do
14
+ let(:scripts) { Scripts.new(Redis.new, "foo") }
15
+
16
+ it "runs the script" do
17
+ scripts.fairway_register_queue("namespace", "name", "topic")
18
+ end
19
+
20
+ context "when the script does not exist" do
21
+ it "loads the script" do
22
+ Redis.new.script(:flush)
23
+ scripts.fairway_register_queue("namespace", "name", "topic")
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,50 @@
1
+ require "spec_helper"
2
+
3
+ module Fairway::Sidekiq
4
+ describe CompositeFetch do
5
+ describe "#initialize" do
6
+ it "accepts a hash of fetches with priority" do
7
+ fetcher = CompositeFetch.new(fetcherA: 10, fetcherB: 1)
8
+ fetcher.fetches.should == [Array.new(10, :fetcherA), :fetcherB].flatten
9
+ end
10
+ end
11
+
12
+ describe "#fetch_order" do
13
+ let(:fetcher) { CompositeFetch.new(fetcherA: 10, fetcherB: 1) }
14
+
15
+ it "should shuffle and uniq fetches" do
16
+ fetcher.fetches.should_receive(:shuffle).and_return(fetcher.fetches)
17
+ fetcher.fetch_order
18
+ end
19
+
20
+ it "should unique fetches list" do
21
+ fetcher.fetches.length.should == 11
22
+ fetcher.fetch_order.length.should == 2
23
+ end
24
+ end
25
+
26
+ describe "#retrieve_work" do
27
+ let(:work) { mock(:work) }
28
+ let(:fetcherA) { mock(:fetcher) }
29
+ let(:fetcherB) { mock(:fetcher) }
30
+ let(:fetcher) { CompositeFetch.new(fetcherA => 10, fetcherB => 1) }
31
+
32
+ before do
33
+ fetcher.stub(fetch_order: [fetcherA, fetcherB])
34
+ end
35
+
36
+ it "returns work from the first fetcher who has work" do
37
+ fetcherA.stub(retrieve_work: work)
38
+ fetcherB.should_not_receive(:retrieve_work)
39
+
40
+ fetcher.retrieve_work.should == work
41
+ end
42
+
43
+ it "attempts to retrieve work from each fetcher if no work is found" do
44
+ fetcherA.should_receive(:retrieve_work)
45
+ fetcherB.should_receive(:retrieve_work)
46
+ fetcher.retrieve_work.should be_nil
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,31 @@
1
+ require "spec_helper"
2
+
3
+ module Fairway::Sidekiq
4
+ describe Fetcher do
5
+ let(:manager) { mock(:manager) }
6
+ let(:fetch) { mock(:fetch) }
7
+
8
+ it "accepts a manager and a fetch strategy" do
9
+ fetcher = Fetcher.new(manager, fetch)
10
+ fetcher.mgr.should == manager
11
+ fetcher.strategy.should == fetch
12
+ end
13
+
14
+ describe "#fetch" do
15
+ let(:fetcher) { Fetcher.new(manager, fetch) }
16
+
17
+ it "retrieves work from fetch strategy" do
18
+ fetch.should_receive(:retrieve_work)
19
+ fetcher.fetch
20
+ end
21
+
22
+ it "tells manager to assign work if work is fetched" do
23
+ work = mock(:work)
24
+ fetch.stub(retrieve_work: work)
25
+ manager.stub(async: manager)
26
+ manager.should_receive(:assign).with(work)
27
+ fetcher.fetch
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ require "spec_helper"
2
+
3
+ module Fairway::Sidekiq
4
+ describe NonBlockingFetch do
5
+ let(:queues) { [:critical, :critical, :default] }
6
+ let(:fetch) { NonBlockingFetch.new(queues: queues) }
7
+
8
+ it "accepts options with a list of queues and their weights" do
9
+ fetch.queues.should == ["queue:critical", "queue:critical", "queue:default"]
10
+ end
11
+
12
+ describe "#retrieve_work" do
13
+ it "calls rpop script with queue order" do
14
+ fetch.stub(queues_cmd: ["queue:default", "queue:critical"])
15
+
16
+ ::Sidekiq.redis do |conn|
17
+ conn.lpush("queue:default", "default")
18
+ conn.lpush("queue:critical", "critical")
19
+ end
20
+
21
+ unit_of_work = fetch.retrieve_work
22
+ unit_of_work.queue_name.should == "default"
23
+ unit_of_work.message.should == "default"
24
+
25
+ unit_of_work = fetch.retrieve_work
26
+ unit_of_work.queue_name.should == "critical"
27
+ unit_of_work.message.should == "critical"
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,35 @@
1
+ require "spec_helper"
2
+
3
+ module Fairway
4
+ module Sidekiq
5
+ describe QueueFetch do
6
+ let(:reader) { QueueReader.new(Connection.new, "fairway") }
7
+ let(:work) { { queue: "golf_events", type: "swing", name: "putt" }.to_json }
8
+
9
+ it "requests work from the queue reader" do
10
+ fetch = QueueFetch.new(reader)
11
+
12
+ reader.stub(pull: ["fairway", work])
13
+
14
+ unit_of_work = fetch.retrieve_work
15
+ unit_of_work.queue_name.should == "golf_events"
16
+ unit_of_work.message.should == work
17
+ end
18
+
19
+ it "allows transforming of the message into a job" do
20
+ fetch = QueueFetch.new(reader) do |fairway_queue, message|
21
+ message.tap do |message|
22
+ message["queue"] = "my_#{message["queue"]}"
23
+ message["class"] = "GolfEventJob"
24
+ end
25
+ end
26
+
27
+ reader.stub(pull: ["fairway", work])
28
+
29
+ unit_of_work = fetch.retrieve_work
30
+ unit_of_work.queue_name.should == "my_golf_events"
31
+ unit_of_work.message.should == JSON.parse(work).merge("queue" => "my_golf_events", "class" => "GolfEventJob").to_json
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,16 @@
1
+ require "spec_helper"
2
+
3
+ # module Fairway
4
+ # describe Subscription do
5
+ # describe "#initialize" do
6
+ # it "requires a Connection"
7
+ # it "requires a pattern"
8
+ # it "requires a block"
9
+ # it "subscribes to the pattern"
10
+ # end
11
+
12
+ # context "when a message is published" do
13
+ # it "calls the block"
14
+ # end
15
+ # end
16
+ # end
@@ -0,0 +1,35 @@
1
+ ENV["RAILS_ENV"] = "test"
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require_relative "../boot"
7
+ require "rspec/autorun"
8
+
9
+ Bundler.require(:default, :test)
10
+
11
+ RSpec.configure do |config|
12
+ config.treat_symbols_as_metadata_keys_with_true_values = true
13
+ end
14
+
15
+ require "sidekiq"
16
+ require "sidekiq/manager"
17
+ require "fairway/sidekiq"
18
+
19
+ # Requires supporting ruby files with custom matchers and macros, etc,
20
+ # in spec/support/ and its subdirectories.
21
+ Dir[File.join(File.dirname(__FILE__), "support/**/*.rb")].each {|f| require f}
22
+
23
+ RSpec.configure do |config|
24
+ config.before(:each) do
25
+ Fairway.configure do |config|
26
+ config.namespace = "test:backbone"
27
+ end
28
+
29
+ Fairway::Config.new.redis.flushdb
30
+ end
31
+
32
+ config.after(:each) do
33
+ Fairway::Config.new.redis.flushdb
34
+ end
35
+ end