pubsubstub 0.0.15 → 0.1.0.beta1
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.
- checksums.yaml +4 -4
- data/.travis.yml +3 -1
- data/Gemfile +8 -0
- data/README.md +10 -4
- data/Rakefile +6 -1
- data/bin/console +16 -0
- data/bin/server +3 -0
- data/example/Gemfile +1 -1
- data/example/config.ru +2 -0
- data/example/puma_config.rb +19 -0
- data/lib/pubsubstub.rb +65 -4
- data/lib/pubsubstub/action.rb +0 -10
- data/lib/pubsubstub/channel.rb +21 -35
- data/lib/pubsubstub/event.rb +10 -6
- data/lib/pubsubstub/logging.rb +15 -0
- data/lib/pubsubstub/publish_action.rb +1 -2
- data/lib/pubsubstub/stream_action.rb +74 -40
- data/lib/pubsubstub/subscriber.rb +88 -0
- data/lib/pubsubstub/subscription.rb +62 -0
- data/lib/pubsubstub/version.rb +1 -1
- data/pubsubstub.gemspec +0 -9
- data/spec/channel_spec.rb +21 -75
- data/spec/publish_action_spec.rb +13 -0
- data/spec/spec_helper.rb +21 -3
- data/spec/stream_action_spec.rb +34 -97
- data/spec/subscriber_spec.rb +45 -0
- data/spec/subscription_spec.rb +70 -0
- data/spec/support/http_helpers.rb +52 -0
- data/spec/support/threading_matchers.rb +58 -0
- metadata +24 -135
- data/lib/pubsubstub/redis_pub_sub.rb +0 -73
- data/spec/redis_pub_sub_spec.rb +0 -121
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Pubsubstub::Subscriber do
|
4
|
+
describe "#start" do
|
5
|
+
let(:channel) { Pubsubstub::Channel.new('plop') }
|
6
|
+
let(:events) { (1..10).map { |i| Pubsubstub::Event.new("refresh ##{i}", id: i) } }
|
7
|
+
|
8
|
+
it "blocks and yield every published events" do
|
9
|
+
published_events = []
|
10
|
+
subject.add_event_listener('plop', -> (event) { published_events << event })
|
11
|
+
subscribe_thread = Thread.new { subject.start }
|
12
|
+
|
13
|
+
expect { subject.subscribed? }.to happen
|
14
|
+
|
15
|
+
events.each(&channel.method(:publish))
|
16
|
+
|
17
|
+
subject.stop
|
18
|
+
expect { !subject.subscribed? }.to happen
|
19
|
+
|
20
|
+
expect(subscribe_thread).to complete
|
21
|
+
|
22
|
+
expect(published_events).to be == events
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#dispatch_event" do
|
27
|
+
let(:event) { Pubsubstub::Event.new('Hello', id: 1) }
|
28
|
+
|
29
|
+
it "calls all listeners of the channel" do
|
30
|
+
events_a = []
|
31
|
+
events_b = []
|
32
|
+
events_c = []
|
33
|
+
|
34
|
+
subject.add_event_listener('plop', ->(event) { events_a << event })
|
35
|
+
subject.add_event_listener('plop', ->(event) { events_b << event })
|
36
|
+
subject.add_event_listener('foo', ->(event) { events_c << event })
|
37
|
+
|
38
|
+
subject.send(:dispatch_event, 'plop', event)
|
39
|
+
|
40
|
+
expect(events_a).to be == [event]
|
41
|
+
expect(events_b).to be == [event]
|
42
|
+
expect(events_c).to be_empty
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Pubsubstub::Subscription do
|
4
|
+
let(:event) { Pubsubstub::Event.new('hello') }
|
5
|
+
let(:connection) { [] }
|
6
|
+
let(:channels) { %w(foo bar).map(&Pubsubstub::Channel.method(:new)) }
|
7
|
+
let(:channel) { Pubsubstub::Channel.new('foo') }
|
8
|
+
subject { described_class.new(channels, connection) }
|
9
|
+
|
10
|
+
it "has an id" do
|
11
|
+
expect(subject.id).to be_an Integer
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#push" do
|
15
|
+
it "stores the event in the queue" do
|
16
|
+
expect {
|
17
|
+
subject.push(event)
|
18
|
+
}.to change { subject.queue.size }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#stream" do
|
23
|
+
context "when there is no scrollback" do
|
24
|
+
it "sends an heartbeat event immediately" do
|
25
|
+
channel.publish(event)
|
26
|
+
subscription_thread = Thread.start { subject.stream(event.id) }
|
27
|
+
|
28
|
+
begin
|
29
|
+
expect { connection.size == 1 }.to happen
|
30
|
+
expect(connection.first).to include('event: heartbeat')
|
31
|
+
ensure
|
32
|
+
subscription_thread.kill
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "when there is a scrollback" do
|
38
|
+
it "sends it immediately" do
|
39
|
+
channel.publish(event)
|
40
|
+
subscription_thread = Thread.start { subject.stream(event.id - 1) }
|
41
|
+
|
42
|
+
begin
|
43
|
+
expect { connection.size == 1 }.to happen
|
44
|
+
expect(connection.first).to be == event.to_message
|
45
|
+
ensure
|
46
|
+
subscription_thread.kill
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "when an event is published" do
|
52
|
+
it "forward it to the subscribers" do
|
53
|
+
subscriber_thread = Thread.start { Pubsubstub.subscriber.start }
|
54
|
+
subscription_thread = Thread.start { subject.stream(event.id - 1) }
|
55
|
+
|
56
|
+
begin
|
57
|
+
expect { connection.size == 1 }.to happen
|
58
|
+
expect(connection.first).to include('event: heartbeat')
|
59
|
+
|
60
|
+
channel.publish(event)
|
61
|
+
expect { connection.size == 2 }.to happen
|
62
|
+
expect(connection.last).to be == event.to_message
|
63
|
+
ensure
|
64
|
+
subscription_thread.kill
|
65
|
+
subscriber_thread.kill
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module HTTPHelpers
|
4
|
+
def async_get(uri, headers = {}, retries: 10, &block)
|
5
|
+
uri = URI(uri.to_s)
|
6
|
+
queue = Queue.new
|
7
|
+
Thread.start do
|
8
|
+
begin
|
9
|
+
Net::HTTP.start(uri.host, uri.port) do |http|
|
10
|
+
request = Net::HTTP::Get.new uri.request_uri
|
11
|
+
headers.each do |name, value|
|
12
|
+
request.add_field(name, value)
|
13
|
+
end
|
14
|
+
|
15
|
+
begin
|
16
|
+
http.request request do |response|
|
17
|
+
response.read_body do |chunk|
|
18
|
+
queue.push(chunk) unless chunk.empty?
|
19
|
+
end
|
20
|
+
end
|
21
|
+
rescue Errno::EINVAL, Errno::ECONNREFUSED # Happen once in a while when the server is not 100% ready
|
22
|
+
if retries > 0
|
23
|
+
retries -= 1
|
24
|
+
sleep 0.5
|
25
|
+
retry
|
26
|
+
else
|
27
|
+
raise
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
rescue Errno::ECONNRESET, Errno::EPIPE
|
32
|
+
# The server closed the connection
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
queue
|
37
|
+
end
|
38
|
+
|
39
|
+
ROOT_PATH = File.join(__dir__, '../..')
|
40
|
+
LOG_DIR = File.join(ROOT_PATH, 'tmp')
|
41
|
+
LOG_PATH = File.join(ROOT_PATH, 'tmp/puma.log')
|
42
|
+
def with_background_server
|
43
|
+
Dir.mkdir(LOG_DIR) unless Dir.exist?(LOG_DIR)
|
44
|
+
server_pid = Process.spawn('bin/server', chdir: ROOT_PATH, out: LOG_PATH, err: LOG_PATH)
|
45
|
+
begin
|
46
|
+
yield
|
47
|
+
ensure
|
48
|
+
Process.kill('TERM', server_pid)
|
49
|
+
Process.wait(server_pid)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
RSpec::Matchers.define :complete do
|
2
|
+
match do |thread|
|
3
|
+
thread.join(@wait_time) == thread
|
4
|
+
end
|
5
|
+
|
6
|
+
chain :in_under do |seconds|
|
7
|
+
@wait_time = seconds
|
8
|
+
end
|
9
|
+
|
10
|
+
failure_message do |thread|
|
11
|
+
"expected that thread to complete but it didn't (status=#{thread.status.inspect})"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
RSpec::Matchers.define :happen do
|
16
|
+
match do |condition|
|
17
|
+
success = false
|
18
|
+
@wait_time ||= 0.1
|
19
|
+
until @wait_time < 0
|
20
|
+
@wait_time -= 0.05
|
21
|
+
break if success = condition.call
|
22
|
+
sleep 0.05
|
23
|
+
end
|
24
|
+
success
|
25
|
+
end
|
26
|
+
|
27
|
+
supports_block_expectations
|
28
|
+
|
29
|
+
chain :in_under do |seconds|
|
30
|
+
@wait_time = seconds
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
RSpec::Matchers.define :listen do
|
35
|
+
match do |port|
|
36
|
+
start = Time.now.to_f
|
37
|
+
begin
|
38
|
+
connection = TCPSocket.new('localhost', port)
|
39
|
+
true
|
40
|
+
rescue
|
41
|
+
if (Time.now.to_f - start) < @wait_time
|
42
|
+
retry
|
43
|
+
else
|
44
|
+
false
|
45
|
+
end
|
46
|
+
ensure
|
47
|
+
connection.close if connection
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
chain :in_under do |seconds|
|
52
|
+
@wait_time = seconds
|
53
|
+
end
|
54
|
+
|
55
|
+
failure_message do |port|
|
56
|
+
"expected port #{port} to listen to connection but it didn't"
|
57
|
+
end
|
58
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pubsubstub
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.1.0.beta1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Guillaume Malette
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-04-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sinatra
|
@@ -24,20 +24,6 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.4'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: em-hiredis
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0.2'
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0.2'
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: redis
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,20 +38,6 @@ dependencies:
|
|
52
38
|
- - "~>"
|
53
39
|
- !ruby/object:Gem::Version
|
54
40
|
version: '3.0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: eventmachine
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - "~>"
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '1.0'
|
62
|
-
type: :runtime
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '1.0'
|
69
41
|
- !ruby/object:Gem::Dependency
|
70
42
|
name: bundler
|
71
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,109 +52,13 @@ dependencies:
|
|
80
52
|
- - "~>"
|
81
53
|
- !ruby/object:Gem::Version
|
82
54
|
version: '1.5'
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: rake
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - "~>"
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: '10.2'
|
90
|
-
type: :development
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - "~>"
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: '10.2'
|
97
|
-
- !ruby/object:Gem::Dependency
|
98
|
-
name: rspec
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
100
|
-
requirements:
|
101
|
-
- - '='
|
102
|
-
- !ruby/object:Gem::Version
|
103
|
-
version: 3.1.0
|
104
|
-
type: :development
|
105
|
-
prerelease: false
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
107
|
-
requirements:
|
108
|
-
- - '='
|
109
|
-
- !ruby/object:Gem::Version
|
110
|
-
version: 3.1.0
|
111
|
-
- !ruby/object:Gem::Dependency
|
112
|
-
name: pry-byebug
|
113
|
-
requirement: !ruby/object:Gem::Requirement
|
114
|
-
requirements:
|
115
|
-
- - ">="
|
116
|
-
- !ruby/object:Gem::Version
|
117
|
-
version: '0'
|
118
|
-
type: :development
|
119
|
-
prerelease: false
|
120
|
-
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
requirements:
|
122
|
-
- - ">="
|
123
|
-
- !ruby/object:Gem::Version
|
124
|
-
version: '0'
|
125
|
-
- !ruby/object:Gem::Dependency
|
126
|
-
name: thin
|
127
|
-
requirement: !ruby/object:Gem::Requirement
|
128
|
-
requirements:
|
129
|
-
- - "~>"
|
130
|
-
- !ruby/object:Gem::Version
|
131
|
-
version: '1.6'
|
132
|
-
type: :development
|
133
|
-
prerelease: false
|
134
|
-
version_requirements: !ruby/object:Gem::Requirement
|
135
|
-
requirements:
|
136
|
-
- - "~>"
|
137
|
-
- !ruby/object:Gem::Version
|
138
|
-
version: '1.6'
|
139
|
-
- !ruby/object:Gem::Dependency
|
140
|
-
name: rack-test
|
141
|
-
requirement: !ruby/object:Gem::Requirement
|
142
|
-
requirements:
|
143
|
-
- - ">="
|
144
|
-
- !ruby/object:Gem::Version
|
145
|
-
version: '0'
|
146
|
-
type: :development
|
147
|
-
prerelease: false
|
148
|
-
version_requirements: !ruby/object:Gem::Requirement
|
149
|
-
requirements:
|
150
|
-
- - ">="
|
151
|
-
- !ruby/object:Gem::Version
|
152
|
-
version: '0'
|
153
|
-
- !ruby/object:Gem::Dependency
|
154
|
-
name: timecop
|
155
|
-
requirement: !ruby/object:Gem::Requirement
|
156
|
-
requirements:
|
157
|
-
- - ">="
|
158
|
-
- !ruby/object:Gem::Version
|
159
|
-
version: '0'
|
160
|
-
type: :development
|
161
|
-
prerelease: false
|
162
|
-
version_requirements: !ruby/object:Gem::Requirement
|
163
|
-
requirements:
|
164
|
-
- - ">="
|
165
|
-
- !ruby/object:Gem::Version
|
166
|
-
version: '0'
|
167
|
-
- !ruby/object:Gem::Dependency
|
168
|
-
name: em-spec
|
169
|
-
requirement: !ruby/object:Gem::Requirement
|
170
|
-
requirements:
|
171
|
-
- - ">="
|
172
|
-
- !ruby/object:Gem::Version
|
173
|
-
version: '0'
|
174
|
-
type: :development
|
175
|
-
prerelease: false
|
176
|
-
version_requirements: !ruby/object:Gem::Requirement
|
177
|
-
requirements:
|
178
|
-
- - ">="
|
179
|
-
- !ruby/object:Gem::Version
|
180
|
-
version: '0'
|
181
55
|
description: Pubsubstub can be added to a rack Application or deployed standalone.
|
182
56
|
It uses Redis to do the Pub/Sub
|
183
57
|
email:
|
184
58
|
- gmalette@gmail.com
|
185
|
-
executables:
|
59
|
+
executables:
|
60
|
+
- console
|
61
|
+
- server
|
186
62
|
extensions: []
|
187
63
|
extra_rdoc_files: []
|
188
64
|
files:
|
@@ -193,23 +69,32 @@ files:
|
|
193
69
|
- LICENSE.txt
|
194
70
|
- README.md
|
195
71
|
- Rakefile
|
72
|
+
- bin/console
|
73
|
+
- bin/server
|
196
74
|
- example/Gemfile
|
197
75
|
- example/config.ru
|
76
|
+
- example/puma_config.rb
|
198
77
|
- lib/pubsubstub.rb
|
199
78
|
- lib/pubsubstub/action.rb
|
200
79
|
- lib/pubsubstub/application.rb
|
201
80
|
- lib/pubsubstub/channel.rb
|
202
81
|
- lib/pubsubstub/event.rb
|
82
|
+
- lib/pubsubstub/logging.rb
|
203
83
|
- lib/pubsubstub/publish_action.rb
|
204
|
-
- lib/pubsubstub/redis_pub_sub.rb
|
205
84
|
- lib/pubsubstub/stream_action.rb
|
85
|
+
- lib/pubsubstub/subscriber.rb
|
86
|
+
- lib/pubsubstub/subscription.rb
|
206
87
|
- lib/pubsubstub/version.rb
|
207
88
|
- pubsubstub.gemspec
|
208
89
|
- spec/channel_spec.rb
|
209
90
|
- spec/event_spec.rb
|
210
|
-
- spec/
|
91
|
+
- spec/publish_action_spec.rb
|
211
92
|
- spec/spec_helper.rb
|
212
93
|
- spec/stream_action_spec.rb
|
94
|
+
- spec/subscriber_spec.rb
|
95
|
+
- spec/subscription_spec.rb
|
96
|
+
- spec/support/http_helpers.rb
|
97
|
+
- spec/support/threading_matchers.rb
|
213
98
|
homepage: https://github.com/gmalette/pubsubstub
|
214
99
|
licenses:
|
215
100
|
- MIT
|
@@ -225,18 +110,22 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
225
110
|
version: '0'
|
226
111
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
227
112
|
requirements:
|
228
|
-
- - "
|
113
|
+
- - ">"
|
229
114
|
- !ruby/object:Gem::Version
|
230
|
-
version:
|
115
|
+
version: 1.3.1
|
231
116
|
requirements: []
|
232
117
|
rubyforge_project:
|
233
|
-
rubygems_version: 2.
|
118
|
+
rubygems_version: 2.5.1
|
234
119
|
signing_key:
|
235
120
|
specification_version: 4
|
236
121
|
summary: Pubsubstub is a rack middleware to add Pub/Sub
|
237
122
|
test_files:
|
238
123
|
- spec/channel_spec.rb
|
239
124
|
- spec/event_spec.rb
|
240
|
-
- spec/
|
125
|
+
- spec/publish_action_spec.rb
|
241
126
|
- spec/spec_helper.rb
|
242
127
|
- spec/stream_action_spec.rb
|
128
|
+
- spec/subscriber_spec.rb
|
129
|
+
- spec/subscription_spec.rb
|
130
|
+
- spec/support/http_helpers.rb
|
131
|
+
- spec/support/threading_matchers.rb
|