qup 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +41 -0
- data/.gemtest +1 -0
- data/ADAPTER_API.rdoc +97 -0
- data/HISTORY.rdoc +9 -0
- data/Manifest.txt +52 -0
- data/README.rdoc +156 -0
- data/Rakefile +246 -0
- data/lib/qup.rb +48 -0
- data/lib/qup/adapter.rb +57 -0
- data/lib/qup/adapter/kestrel.rb +56 -0
- data/lib/qup/adapter/kestrel/destination.rb +54 -0
- data/lib/qup/adapter/kestrel/queue.rb +101 -0
- data/lib/qup/adapter/kestrel/topic.rb +68 -0
- data/lib/qup/adapter/maildir.rb +57 -0
- data/lib/qup/adapter/maildir/queue.rb +123 -0
- data/lib/qup/adapter/maildir/topic.rb +85 -0
- data/lib/qup/adapter/redis.rb +55 -0
- data/lib/qup/adapter/redis/connection.rb +32 -0
- data/lib/qup/adapter/redis/queue.rb +97 -0
- data/lib/qup/adapter/redis/topic.rb +76 -0
- data/lib/qup/consumer.rb +42 -0
- data/lib/qup/message.rb +18 -0
- data/lib/qup/producer.rb +28 -0
- data/lib/qup/publisher.rb +30 -0
- data/lib/qup/queue_api.rb +124 -0
- data/lib/qup/session.rb +111 -0
- data/lib/qup/subscriber.rb +28 -0
- data/lib/qup/topic_api.rb +92 -0
- data/spec/qup/adapter/kestrel/queue_spec.rb +9 -0
- data/spec/qup/adapter/kestrel/topic_spec.rb +9 -0
- data/spec/qup/adapter/kestrel_context.rb +8 -0
- data/spec/qup/adapter/kestrel_spec.rb +8 -0
- data/spec/qup/adapter/maildir/queue_spec.rb +9 -0
- data/spec/qup/adapter/maildir/topic_spec.rb +9 -0
- data/spec/qup/adapter/maildir_context.rb +10 -0
- data/spec/qup/adapter/maildir_spec.rb +8 -0
- data/spec/qup/adapter/redis/queue_spec.rb +9 -0
- data/spec/qup/adapter/redis/topic_spec.rb +9 -0
- data/spec/qup/adapter/redis_context.rb +6 -0
- data/spec/qup/adapter/redis_spec.rb +8 -0
- data/spec/qup/adapter_spec.rb +28 -0
- data/spec/qup/consumer_spec.rb +40 -0
- data/spec/qup/message_spec.rb +13 -0
- data/spec/qup/producer_spec.rb +18 -0
- data/spec/qup/queue_api_spec.rb +21 -0
- data/spec/qup/session_spec.rb +81 -0
- data/spec/qup/shared_adapter_examples.rb +29 -0
- data/spec/qup/shared_queue_examples.rb +71 -0
- data/spec/qup/shared_topic_examples.rb +57 -0
- data/spec/qup/topic_api_spec.rb +21 -0
- data/spec/qup_spec.rb +37 -0
- data/spec/spec_helper.rb +26 -0
- metadata +281 -0
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'qup/shared_queue_examples'
|
3
|
+
require 'qup/adapter/kestrel_context'
|
4
|
+
|
5
|
+
describe 'Qup::Adapter::Kestrel::Queue', :kestrel => true do
|
6
|
+
include_context "Qup::Adapter::Kestrel"
|
7
|
+
include_context "Qup::Queue"
|
8
|
+
it_behaves_like Qup::QueueAPI
|
9
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'qup/shared_topic_examples'
|
3
|
+
require 'qup/adapter/kestrel_context'
|
4
|
+
|
5
|
+
describe 'Qup::Adapter::Kestrel::Topic', :kestrel => true do
|
6
|
+
include_context "Qup::Adapter::Kestrel"
|
7
|
+
include_context "Qup::Topic"
|
8
|
+
it_behaves_like Qup::TopicAPI
|
9
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# The Shared Context that all the Kestrel tests need to run.
|
2
|
+
# It is assumed that there is a Kestrel server running on localhost port 22133
|
3
|
+
shared_context "Qup::Adapter::Kestrel" do
|
4
|
+
let( :uri ) { URI.parse( "kestrel://localhost:22133/" ) }
|
5
|
+
let( :adapter ) { ::Qup::Adapter::Kestrel.new( uri ) }
|
6
|
+
end
|
7
|
+
|
8
|
+
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'qup/shared_queue_examples'
|
3
|
+
require 'qup/adapter/maildir_context'
|
4
|
+
|
5
|
+
describe 'Qup::Adapter::Maildir::Queue', :maildir => true do
|
6
|
+
include_context "Qup::Adapter::Maildir"
|
7
|
+
include_context "Qup::Queue"
|
8
|
+
it_behaves_like Qup::QueueAPI
|
9
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'qup/shared_topic_examples'
|
3
|
+
require 'qup/adapter/maildir_context'
|
4
|
+
|
5
|
+
describe 'Qup::Adapter::Maildir::Topic', :maildir => true do
|
6
|
+
include_context "Qup::Adapter::Maildir"
|
7
|
+
include_context "Qup::Topic"
|
8
|
+
it_behaves_like Qup::TopicAPI
|
9
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# The common context needed for all the Maildir Adapter tests.
|
2
|
+
#
|
3
|
+
# All the maildir tests will be run in the 'path' below
|
4
|
+
shared_context 'Qup::Adapter::Maildir' do
|
5
|
+
let( :path ) { temp_dir( "qup-maildir" ) }
|
6
|
+
let( :uri ) { URI.parse( "maildir://#{path}" ) }
|
7
|
+
|
8
|
+
# Needed to support the Shared Examples
|
9
|
+
let( :adapter ) { ::Qup::Adapter::Maildir.new( uri ) }
|
10
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'qup/shared_queue_examples'
|
3
|
+
require 'qup/adapter/redis_context'
|
4
|
+
|
5
|
+
describe 'Qup::Adapter::Redis::Queue', :redis => true do
|
6
|
+
include_context "Qup::Adapter::Redis"
|
7
|
+
include_context "Qup::Queue"
|
8
|
+
it_behaves_like Qup::QueueAPI
|
9
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'qup/shared_topic_examples'
|
3
|
+
require 'qup/adapter/redis_context'
|
4
|
+
|
5
|
+
describe 'Qup::Adapter::Redis::Topic', :redis => true do
|
6
|
+
include_context "Qup::Adapter::Redis"
|
7
|
+
include_context "Qup::Topic"
|
8
|
+
it_behaves_like Qup::TopicAPI
|
9
|
+
end
|
@@ -0,0 +1,6 @@
|
|
1
|
+
# The shared context that all the Redis tests need to run.
|
2
|
+
# It is assumed that there is a Redis server running on localhost port 6379
|
3
|
+
shared_context "Qup::Adapter::Redis" do
|
4
|
+
let( :uri ) { URI.parse( "redis://localhost:6379/" ) }
|
5
|
+
let( :adapter ) { ::Qup::Adapter::Redis.new( uri ) }
|
6
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class Qup::AdapterTest < Qup::Adapter
|
4
|
+
register :quptest
|
5
|
+
end
|
6
|
+
|
7
|
+
describe 'Adapter Registration' do
|
8
|
+
it 'registers an adapter' do
|
9
|
+
Qup::Adapters['quptest'].should eq Qup::AdapterTest
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "Not Implementing the Adapter API" do
|
14
|
+
let( :api ) { Qup::AdapterTest.new }
|
15
|
+
|
16
|
+
%w[ close closed? ].each do |method|
|
17
|
+
it "##{method} kaboom!" do
|
18
|
+
lambda { api.send( method ) }.should raise_error( NotImplementedError, "please implement '#{method}'" )
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
%w[ queue topic ].each do |method|
|
23
|
+
it "##{method} kaboom!" do
|
24
|
+
lambda { api.send( method, 'foo' ) }.should raise_error( NotImplementedError, "please implement '#{method}'" )
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Qup::Consumer do
|
4
|
+
|
5
|
+
let( :path ) { temp_dir( "qup-consumer" ) }
|
6
|
+
let( :queue ) { ::Qup::Adapter::Maildir::Queue.new( path, 'bar' ) }
|
7
|
+
let( :producer ) { queue.producer }
|
8
|
+
let( :consumer ) { queue.consumer }
|
9
|
+
|
10
|
+
before do
|
11
|
+
producer.produce( 'consumption' )
|
12
|
+
end
|
13
|
+
|
14
|
+
after do
|
15
|
+
FileUtils.rm_rf( path )
|
16
|
+
end
|
17
|
+
|
18
|
+
it "consumes an item from the queue" do
|
19
|
+
msg = consumer.consume
|
20
|
+
msg.data.should eq 'consumption'
|
21
|
+
queue.acknowledge msg
|
22
|
+
queue.depth.should eq 0
|
23
|
+
end
|
24
|
+
|
25
|
+
it "acknowledges messages it has consumed" do
|
26
|
+
msg = consumer.consume
|
27
|
+
msg.data.should eq 'consumption'
|
28
|
+
queue.depth.should eq 1
|
29
|
+
consumer.acknowledge( msg )
|
30
|
+
queue.depth.should eq 0
|
31
|
+
end
|
32
|
+
|
33
|
+
it "consumes auto-acknowledges msgs in a block" do
|
34
|
+
consumer.consume do |msg|
|
35
|
+
msg.data.should eq 'consumption'
|
36
|
+
end
|
37
|
+
queue.depth.should eq 0
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Qup::Message do
|
4
|
+
let( :message ) { Qup::Message.new( "my unique key", "some data" ) }
|
5
|
+
|
6
|
+
it "has a key" do
|
7
|
+
message.key.should == 'my unique key'
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'has data' do
|
11
|
+
message.data.should == 'some data'
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Qup::Producer do
|
4
|
+
|
5
|
+
let( :path ) { temp_dir( "qup-producer" ) }
|
6
|
+
let( :queue ) { ::Qup::Adapter::Maildir::Queue.new( path, 'baz' ) }
|
7
|
+
let( :producer ) { queue.producer }
|
8
|
+
|
9
|
+
after do
|
10
|
+
FileUtils.rm_rf( path )
|
11
|
+
end
|
12
|
+
|
13
|
+
it "produces items onto the queue" do
|
14
|
+
queue.depth.should eq 0
|
15
|
+
producer.produce( 'production' )
|
16
|
+
queue.depth.should eq 1
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class Qup::QueueAPITest
|
4
|
+
include Qup::QueueAPI
|
5
|
+
end
|
6
|
+
|
7
|
+
describe "Not Implementing the Queue API" do
|
8
|
+
let( :api ) { Qup::QueueAPITest.new }
|
9
|
+
|
10
|
+
%w[ name depth flush destroy consume ].each do |method|
|
11
|
+
it "##{method} kaboom!" do
|
12
|
+
lambda { api.send( method ) }.should raise_error( NotImplementedError, "please implement '#{method}'" )
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
%w[ produce acknowledge ].each do |method|
|
17
|
+
it "##{method} kaboom!" do
|
18
|
+
lambda { api.send( method, nil ) }.should raise_error( NotImplementedError, "please implement '#{method}'" )
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Qup::Session do
|
4
|
+
|
5
|
+
let( :path ) { temp_dir( "qup-session" ) }
|
6
|
+
let( :uri ) { "maildir://#{path}" }
|
7
|
+
let( :session ) { ::Qup::Session.new( uri ) }
|
8
|
+
|
9
|
+
after do
|
10
|
+
FileUtils.rm_rf( path )
|
11
|
+
end
|
12
|
+
|
13
|
+
it "has a uri" do
|
14
|
+
session.uri.to_s.should == "maildir:#{path}"
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'can be closed' do
|
18
|
+
session.closed?.should be_false
|
19
|
+
session.close
|
20
|
+
session.closed?.should be_true
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#open' do
|
24
|
+
it 'returns a new session' do
|
25
|
+
s = Qup::Session.open( uri )
|
26
|
+
s.closed?.should be_false
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'yields a new session' do
|
30
|
+
Qup::Session.open( uri ) do |s|
|
31
|
+
s.closed?.should be_false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'closes a session at the end of the block' do
|
36
|
+
save_s = nil
|
37
|
+
Qup::Session.open( uri ) do |s|
|
38
|
+
s.closed?.should be_false
|
39
|
+
save_s = s
|
40
|
+
end
|
41
|
+
save_s.closed?.should be_true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#queue' do
|
46
|
+
it "can return a Queue" do
|
47
|
+
q = session.queue( 'foo' )
|
48
|
+
q.name.should == 'foo'
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'can yield a Queue' do
|
52
|
+
session.queue( 'foo' ) do |q|
|
53
|
+
q.name.should == 'foo'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'raises an error if accessing a closed Session' do
|
58
|
+
session.close
|
59
|
+
lambda { session.queue( 'boom' ) }.should raise_error( Qup::Session::ClosedError, /Session (.*) is closed/ )
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
describe '#topic' do
|
65
|
+
it "can return a Topic" do
|
66
|
+
t = session.topic('t')
|
67
|
+
t.name.should == 't'
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'can yiled a Topic' do
|
71
|
+
session.topic('t') do |t|
|
72
|
+
t.name.should == 't'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'raises an error if accessing a closed Session' do
|
77
|
+
session.close
|
78
|
+
lambda { session.topic( 'boom' ) }.should raise_error( Qup::Session::ClosedError, /Session (.*) is closed/ )
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# The Adapter share context requires that the context is include in define:
|
4
|
+
#
|
5
|
+
# let( :adapter )
|
6
|
+
#
|
7
|
+
shared_examples Qup::Adapter do
|
8
|
+
it 'is registered as an adapter' do
|
9
|
+
Qup::Adapters[uri.scheme].should eq adapter.class
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'can be closed' do
|
13
|
+
adapter.closed?.should be_false
|
14
|
+
adapter.close
|
15
|
+
adapter.closed?.should be_true
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'can create a QueueAPI-like object' do
|
19
|
+
q = adapter.queue( 'q' )
|
20
|
+
q.should be_kind_of( Qup::QueueAPI )
|
21
|
+
q.name.should eq 'q'
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'can create a QueueAPI-like object' do
|
25
|
+
t = adapter.topic( 't' )
|
26
|
+
t.should be_kind_of( Qup::TopicAPI )
|
27
|
+
t.name.should eq 't'
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# The Queue share context requires that the context is include in define:
|
4
|
+
#
|
5
|
+
# let( :adapter )
|
6
|
+
#
|
7
|
+
shared_context "Qup::Queue" do
|
8
|
+
|
9
|
+
let( :queue ) { adapter.queue( 'foo' ) }
|
10
|
+
|
11
|
+
after do
|
12
|
+
queue.destroy
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
shared_examples Qup::QueueAPI do
|
18
|
+
|
19
|
+
it "has a name" do
|
20
|
+
queue.name.should eq 'foo'
|
21
|
+
end
|
22
|
+
|
23
|
+
it "#produce" do
|
24
|
+
queue.depth.should eq 0
|
25
|
+
queue.produce( "a new message" )
|
26
|
+
queue.depth.should eq 1
|
27
|
+
end
|
28
|
+
|
29
|
+
it "#flush" do
|
30
|
+
10.times { |x| queue.produce( "message #{x}" ) }
|
31
|
+
queue.depth.should eq 10
|
32
|
+
queue.flush
|
33
|
+
queue.depth.should eq 0
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '#consume' do
|
37
|
+
before do
|
38
|
+
queue.produce( "consumeable message" )
|
39
|
+
queue.depth.should eq 1
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'normally' do
|
43
|
+
msg = queue.consume
|
44
|
+
msg.data.should eq "consumeable message"
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'with block it auto acknowledges' do
|
48
|
+
queue.consume do |msg|
|
49
|
+
msg.data.should eq 'consumeable message'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "#acknowledge" do
|
55
|
+
it "acks a message" do
|
56
|
+
queue.produce( "acknowledgeable message" )
|
57
|
+
queue.depth.should eq 1
|
58
|
+
|
59
|
+
msg = queue.consume
|
60
|
+
msg.data.should eq "acknowledgeable message"
|
61
|
+
|
62
|
+
queue.acknowledge( msg )
|
63
|
+
queue.depth.should eq 0
|
64
|
+
end
|
65
|
+
|
66
|
+
it "raises an error if you attempt to to acknowledge an unconsumed message" do
|
67
|
+
msg = queue.produce( 'unconsumed' )
|
68
|
+
lambda { queue.acknowledge( msg ) }.should raise_error(Qup::Error)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# The Queue share context requires that the context is include in define:
|
4
|
+
#
|
5
|
+
# let( :adapter )
|
6
|
+
#
|
7
|
+
shared_context "Qup::Topic" do
|
8
|
+
|
9
|
+
before do
|
10
|
+
@topic = adapter.topic( 'topic' )
|
11
|
+
end
|
12
|
+
|
13
|
+
after do
|
14
|
+
@topic.destroy
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
shared_examples Qup::TopicAPI do
|
20
|
+
|
21
|
+
it "has a name" do
|
22
|
+
@topic.name.should == 'topic'
|
23
|
+
end
|
24
|
+
|
25
|
+
it "creates publisher" do
|
26
|
+
p = @topic.publisher
|
27
|
+
p.topic.should eq @topic
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
describe "subscribers" do
|
32
|
+
before do
|
33
|
+
@subs = []
|
34
|
+
3.times do |x|
|
35
|
+
@subs << @topic.subscriber( "sub-#{x}")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
after do
|
40
|
+
@subs.each { |s| s.unsubscribe }
|
41
|
+
end
|
42
|
+
|
43
|
+
it "are counted" do
|
44
|
+
@topic.subscriber_count.should eq 3
|
45
|
+
end
|
46
|
+
|
47
|
+
it "each receives a copy of the message" do
|
48
|
+
p = @topic.publisher
|
49
|
+
p.publish( "hi all" )
|
50
|
+
|
51
|
+
@subs.each do |sub|
|
52
|
+
msg = sub.consume
|
53
|
+
msg.data.should eq 'hi all'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|