fairway 0.0.1

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.
@@ -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