hivent 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.codeclimate.yml +19 -0
- data/.gitignore +14 -0
- data/.rspec +1 -0
- data/.rubocop.yml +1063 -0
- data/.ruby-version +1 -0
- data/.simplecov.template +1 -0
- data/.travis.yml +23 -0
- data/.version +1 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +196 -0
- data/bin/hivent +5 -0
- data/hivent.gemspec +34 -0
- data/lib/hivent.rb +32 -0
- data/lib/hivent/abstract_signal.rb +63 -0
- data/lib/hivent/cli/consumer.rb +60 -0
- data/lib/hivent/cli/runner.rb +50 -0
- data/lib/hivent/cli/start_option_parser.rb +53 -0
- data/lib/hivent/config.rb +22 -0
- data/lib/hivent/config/options.rb +51 -0
- data/lib/hivent/emitter.rb +41 -0
- data/lib/hivent/life_cycle_event_handler.rb +41 -0
- data/lib/hivent/redis/consumer.rb +82 -0
- data/lib/hivent/redis/extensions.rb +26 -0
- data/lib/hivent/redis/lua/consumer.lua +179 -0
- data/lib/hivent/redis/lua/producer.lua +27 -0
- data/lib/hivent/redis/producer.rb +24 -0
- data/lib/hivent/redis/redis.rb +14 -0
- data/lib/hivent/redis/signal.rb +36 -0
- data/lib/hivent/rspec.rb +11 -0
- data/lib/hivent/signal.rb +14 -0
- data/lib/hivent/spec.rb +11 -0
- data/lib/hivent/spec/matchers.rb +14 -0
- data/lib/hivent/spec/matchers/emit.rb +116 -0
- data/lib/hivent/spec/signal.rb +60 -0
- data/lib/hivent/version.rb +6 -0
- data/spec/codeclimate_helper.rb +5 -0
- data/spec/fixtures/cli/bootstrap_consumers.rb +7 -0
- data/spec/fixtures/cli/life_cycle_event_test.rb +8 -0
- data/spec/hivent/abstract_signal_spec.rb +161 -0
- data/spec/hivent/cli/consumer_spec.rb +68 -0
- data/spec/hivent/cli/runner_spec.rb +75 -0
- data/spec/hivent/cli/start_option_parser_spec.rb +48 -0
- data/spec/hivent/life_cycle_event_handler_spec.rb +38 -0
- data/spec/hivent/redis/consumer_spec.rb +348 -0
- data/spec/hivent/redis/signal_spec.rb +155 -0
- data/spec/hivent_spec.rb +100 -0
- data/spec/spec/matchers/emit_spec.rb +66 -0
- data/spec/spec/signal_spec.rb +72 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/support/matchers/exit_with_code.rb +28 -0
- data/spec/support/stdout_helpers.rb +25 -0
- metadata +267 -0
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
describe Hivent::CLI::Consumer do
|
5
|
+
|
6
|
+
let(:consumer) { Hivent::CLI::Consumer.new(options) }
|
7
|
+
let(:options) { {} }
|
8
|
+
|
9
|
+
let(:life_cycle_event_handler) { double("Hivent::LifeCycleEventHandler").as_null_object }
|
10
|
+
let(:require_file) { File.expand_path("../../../fixtures/cli/bootstrap_consumers.rb", __FILE__) }
|
11
|
+
|
12
|
+
before :each do
|
13
|
+
allow(Hivent::Config).to receive(:life_cycle_event_handler).and_return(life_cycle_event_handler)
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#run!" do
|
17
|
+
|
18
|
+
subject { silence { consumer.run! } }
|
19
|
+
let(:options) { { require: require_file } }
|
20
|
+
let(:redis) { Redis.new(url: REDIS_URL) }
|
21
|
+
let(:service_name) { "test" }
|
22
|
+
let(:partition_count) { 2 }
|
23
|
+
let(:redis_consumer_double) { double("Hivent::Redis::Consumer").as_null_object }
|
24
|
+
|
25
|
+
before :each do
|
26
|
+
allow(Hivent::Redis::Consumer).to receive(:new).and_return(redis_consumer_double)
|
27
|
+
end
|
28
|
+
|
29
|
+
after :each do
|
30
|
+
redis.flushall
|
31
|
+
Hivent.emitter.events.clear
|
32
|
+
end
|
33
|
+
|
34
|
+
it "registers the partition count for the service" do
|
35
|
+
expect { subject }.to change {
|
36
|
+
redis.get("#{service_name}:partition_count")
|
37
|
+
}.from(nil).to(partition_count.to_s)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "registers events for the service" do
|
41
|
+
Hivent.emitter.events.push({ name: "my:event", version: 1 }, name: "my:event2")
|
42
|
+
expect { subject }.to change {
|
43
|
+
[redis.smembers("my:event"), redis.smembers("my:event2")].flatten
|
44
|
+
}.from([]).to([service_name, service_name])
|
45
|
+
end
|
46
|
+
|
47
|
+
it "notifies the life cycle event handler about registration of events" do
|
48
|
+
events = [{ name: "my:event", version: 1 }, { name: "my:event2" }]
|
49
|
+
Hivent.emitter.events.push(*events)
|
50
|
+
|
51
|
+
expect(life_cycle_event_handler).to receive(:application_registered).with(service_name, events, partition_count)
|
52
|
+
subject
|
53
|
+
end
|
54
|
+
|
55
|
+
it "starts the consumer" do
|
56
|
+
double = double().as_null_object
|
57
|
+
|
58
|
+
allow(Hivent::Redis::Consumer).to receive(:new)
|
59
|
+
.with(instance_of(Redis), service_name, "#{Socket.gethostname}:#{Process.pid}", life_cycle_event_handler)
|
60
|
+
.and_return(double)
|
61
|
+
|
62
|
+
subject
|
63
|
+
expect(double).to have_received(:run!).once
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
describe Hivent::CLI::Runner do
|
5
|
+
|
6
|
+
describe "#run" do
|
7
|
+
|
8
|
+
subject { runner.run }
|
9
|
+
|
10
|
+
let(:runner) { described_class.new(([command] + args).compact) }
|
11
|
+
let(:args) { [] }
|
12
|
+
let(:require_file) { File.expand_path("../../../fixtures/cli/bootstrap_consumers.rb", __FILE__) }
|
13
|
+
|
14
|
+
context "with no command" do
|
15
|
+
|
16
|
+
let(:command) { nil }
|
17
|
+
|
18
|
+
it "prints help for available commands" do
|
19
|
+
expect(with_captured_stdout { subject }).to include("Available COMMANDs are")
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
context "with unknown command" do
|
25
|
+
|
26
|
+
let(:command) { "unknown" }
|
27
|
+
|
28
|
+
it "prints help for available commands" do
|
29
|
+
expect(with_captured_stdout { subject }).to include("Available COMMANDs are")
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
context "with --help option" do
|
35
|
+
|
36
|
+
let(:args) { ["--help"] }
|
37
|
+
|
38
|
+
["start"].each do |cmd|
|
39
|
+
|
40
|
+
context "with #{cmd} command" do
|
41
|
+
|
42
|
+
let(:command) { cmd }
|
43
|
+
|
44
|
+
it "prints help for #{cmd} command options" do
|
45
|
+
output = with_captured_stdout do
|
46
|
+
begin
|
47
|
+
subject
|
48
|
+
rescue SystemExit
|
49
|
+
# do nothing
|
50
|
+
end
|
51
|
+
end
|
52
|
+
expect(output).to include("Usage: hivent #{cmd} [options]")
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
context "with start command and all required arguments" do
|
62
|
+
|
63
|
+
let(:command) { "start" }
|
64
|
+
let(:args) { ["--require", require_file] }
|
65
|
+
|
66
|
+
it "starts the consumer" do
|
67
|
+
expect(Hivent::CLI::Consumer).to receive(:run!).once
|
68
|
+
subject
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
describe Hivent::CLI::StartOptionParser do
|
5
|
+
|
6
|
+
let(:parser) { described_class.new(:start, args) }
|
7
|
+
let(:args) { [] }
|
8
|
+
let(:require_file) { File.expand_path("../../../fixtures/cli/bootstrap_consumers.rb", __FILE__) }
|
9
|
+
|
10
|
+
describe "#parse" do
|
11
|
+
|
12
|
+
subject { silence { parser.parse } }
|
13
|
+
|
14
|
+
context "when --require option is omitted" do
|
15
|
+
|
16
|
+
it "terminates" do
|
17
|
+
expect { subject }.to exit_with_code(1)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
context "when --require is given" do
|
23
|
+
|
24
|
+
let(:args) { ["--require", require_file] }
|
25
|
+
|
26
|
+
it "does not terminate" do
|
27
|
+
expect { subject }.not_to exit_with_code(1)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "sets options for require" do
|
31
|
+
expect(subject[:require]).to eq(args.last)
|
32
|
+
end
|
33
|
+
|
34
|
+
context "but does not exist" do
|
35
|
+
|
36
|
+
let(:args) { ["--require", "/does/not/exist.rb"] }
|
37
|
+
|
38
|
+
it "terminates" do
|
39
|
+
expect { subject }.to exit_with_code(1)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
describe Hivent::LifeCycleEventHandler do
|
5
|
+
|
6
|
+
subject { silence { consumer.run! } }
|
7
|
+
|
8
|
+
let(:consumer) { Hivent::CLI::Consumer.new(require: require_file) }
|
9
|
+
let(:require_file) { File.expand_path("../../fixtures/cli/life_cycle_event_test.rb", __FILE__) }
|
10
|
+
let(:redis_consumer_double) { double("Hivent::Redis::Consumer").as_null_object }
|
11
|
+
|
12
|
+
let(:redis) { Redis.new(url: REDIS_URL) }
|
13
|
+
let(:handler_class) { Class.new(described_class) }
|
14
|
+
|
15
|
+
let(:event) { { name: "my:event", version: 1 } }
|
16
|
+
|
17
|
+
before :each do
|
18
|
+
stub_const("MyHandler", handler_class) # MyHandler is used in require file
|
19
|
+
allow(Hivent::Redis::Consumer).to receive(:new).and_return(redis_consumer_double)
|
20
|
+
end
|
21
|
+
|
22
|
+
after :each do
|
23
|
+
redis.flushall
|
24
|
+
Hivent.emitter.events.clear
|
25
|
+
end
|
26
|
+
|
27
|
+
it "notifies custom life cycle event handler about registration of events" do
|
28
|
+
Hivent.emitter.events.push(event)
|
29
|
+
expect_any_instance_of(handler_class).to receive(:application_registered)
|
30
|
+
subject
|
31
|
+
end
|
32
|
+
|
33
|
+
it "passes life cycle event handler to redis consumer" do
|
34
|
+
expect(Hivent::Redis::Consumer).to receive(:new).with(anything, anything, anything, instance_of(handler_class))
|
35
|
+
subject
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,348 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
describe Hivent::Redis::Consumer do
|
5
|
+
|
6
|
+
let(:consumer) { described_class.new(redis, service_name, consumer_name, life_cycle_event_handler) }
|
7
|
+
let(:redis) { Redis.new(url: REDIS_URL) }
|
8
|
+
let(:service_name) { "a_service" }
|
9
|
+
let(:consumer_name) { "a_consumer" }
|
10
|
+
let(:life_cycle_event_handler) { double("Hivent::LifeCycleEventHandler").as_null_object }
|
11
|
+
|
12
|
+
after :each do
|
13
|
+
redis.flushall
|
14
|
+
|
15
|
+
Hivent.emitter.__events.clear
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "#queues" do
|
19
|
+
def balance(consumers)
|
20
|
+
# 1. Marks every consumer as "alive"
|
21
|
+
# 2. Resets every consumer
|
22
|
+
# 3. Distributes partitions evenly
|
23
|
+
3.times do
|
24
|
+
consumers.each(&:queues)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
before :each do
|
29
|
+
redis.set("#{service_name}:partition_count", partition_count)
|
30
|
+
end
|
31
|
+
|
32
|
+
context "with a single consumer" do
|
33
|
+
subject { consumer.queues }
|
34
|
+
|
35
|
+
let(:partition_count) { 2 }
|
36
|
+
|
37
|
+
it "returns all available partitions" do
|
38
|
+
expect(subject.length).to eq(partition_count)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context "with two consumers and two partitions" do
|
43
|
+
let(:consumer1) { described_class.new(redis, service_name, "#{consumer_name}1", life_cycle_event_handler) }
|
44
|
+
let(:consumer2) { described_class.new(redis, service_name, "#{consumer_name}2", life_cycle_event_handler) }
|
45
|
+
let(:partition_count) { 2 }
|
46
|
+
|
47
|
+
context "when only one consumer is alive" do
|
48
|
+
before :each do
|
49
|
+
# Hearbeat from first consumer,
|
50
|
+
# marking it as "alive"
|
51
|
+
consumer1.queues
|
52
|
+
end
|
53
|
+
|
54
|
+
it "assigns all available partitions to the living consumer" do
|
55
|
+
distribution = [consumer1.queues, consumer2.queues]
|
56
|
+
|
57
|
+
expect(distribution.map(&:length)).to eq([2, 0])
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "balancing" do
|
61
|
+
it "resets the first consumer for rebalancing" do
|
62
|
+
# Marks consumer 1 as alive, assigning all partitions
|
63
|
+
consumer1.queues
|
64
|
+
# Marks consumer 2 as alive, assigning 0 partitions to
|
65
|
+
# start rebalancing
|
66
|
+
consumer2.queues
|
67
|
+
|
68
|
+
# Assigns 0 partitions to finish resetting
|
69
|
+
expect(consumer1.queues.length).to eq(0)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "assigns half the partitions after reset" do
|
73
|
+
# Fully resets
|
74
|
+
consumer1.queues
|
75
|
+
consumer2.queues
|
76
|
+
consumer1.queues
|
77
|
+
|
78
|
+
# Distributes partitions across consumers
|
79
|
+
expect(consumer2.queues.length).to eq(1)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "rebalances partitions across both consumers" do
|
83
|
+
consumer1.queues
|
84
|
+
consumer2.queues
|
85
|
+
consumer1.queues
|
86
|
+
consumer2.queues
|
87
|
+
|
88
|
+
# Distributes partitions across consumers
|
89
|
+
expect(consumer1.queues.length).to eq(1)
|
90
|
+
end
|
91
|
+
|
92
|
+
context "when one of the consumers dies" do
|
93
|
+
before :each do
|
94
|
+
stub_const("#{described_class}::CONSUMER_TTL", 50)
|
95
|
+
|
96
|
+
balance([consumer1, consumer2])
|
97
|
+
count = 0
|
98
|
+
|
99
|
+
while count <= 2
|
100
|
+
consumer2.queues
|
101
|
+
count += 1
|
102
|
+
|
103
|
+
sleep described_class::CONSUMER_TTL.to_f / 1000
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
it "assigns those consumer's partitions to another consumer" do
|
108
|
+
expect(consumer2.queues.length).to eq(2)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context "when both consumers are alive" do
|
115
|
+
subject do
|
116
|
+
[consumer1.queues, consumer2.queues]
|
117
|
+
end
|
118
|
+
|
119
|
+
before :each do
|
120
|
+
balance([consumer1, consumer2])
|
121
|
+
end
|
122
|
+
|
123
|
+
it "returns all available partitions" do
|
124
|
+
expect(subject.map(&:length)).to eq([1, 1])
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
context "with more consumers than partitions" do
|
130
|
+
subject do
|
131
|
+
[consumer1.queues, consumer2.queues]
|
132
|
+
end
|
133
|
+
|
134
|
+
let(:consumer1) { described_class.new(redis, service_name, "#{consumer_name}1", life_cycle_event_handler) }
|
135
|
+
let(:consumer2) { described_class.new(redis, service_name, "#{consumer_name}2", life_cycle_event_handler) }
|
136
|
+
let(:partition_count) { 1 }
|
137
|
+
|
138
|
+
before :each do
|
139
|
+
balance([consumer1, consumer2])
|
140
|
+
end
|
141
|
+
|
142
|
+
it "returns all available partitions" do
|
143
|
+
expect(subject.map(&:length)).to eq([1, 0])
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
context "with fewer consumers than partitions" do
|
148
|
+
subject do
|
149
|
+
[consumer1.queues, consumer2.queues]
|
150
|
+
end
|
151
|
+
|
152
|
+
let(:consumer1) { described_class.new(redis, service_name, "#{consumer_name}1", life_cycle_event_handler) }
|
153
|
+
let(:consumer2) { described_class.new(redis, service_name, "#{consumer_name}2", life_cycle_event_handler) }
|
154
|
+
let(:partition_count) { 3 }
|
155
|
+
|
156
|
+
before :each do
|
157
|
+
balance([consumer1, consumer2])
|
158
|
+
end
|
159
|
+
|
160
|
+
it "returns all available partitions" do
|
161
|
+
expect(subject.map(&:length)).to eq([2, 1])
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
describe "#consume" do
|
168
|
+
subject { consumer.consume }
|
169
|
+
|
170
|
+
let(:partition_count) { 1 }
|
171
|
+
let(:event) do
|
172
|
+
{
|
173
|
+
payload: { foo: "bar" },
|
174
|
+
meta: {
|
175
|
+
name: 'my:event',
|
176
|
+
version: 1,
|
177
|
+
event_uuid: SecureRandom.hex
|
178
|
+
}
|
179
|
+
}
|
180
|
+
end
|
181
|
+
let(:event_name_with_version) do
|
182
|
+
"#{event[:meta][:name]}:#{event[:meta][:version]}"
|
183
|
+
end
|
184
|
+
let(:producer) { Hivent::Redis::Producer.new(redis) }
|
185
|
+
|
186
|
+
before :each do
|
187
|
+
redis.set("#{service_name}:partition_count", partition_count)
|
188
|
+
redis.sadd(event[:meta][:name], service_name)
|
189
|
+
|
190
|
+
producer.write(event[:meta][:name], event.to_json, 0)
|
191
|
+
end
|
192
|
+
|
193
|
+
context "when there are items ready to be consumed" do
|
194
|
+
|
195
|
+
it "emits the item with indifferent access" do
|
196
|
+
Hivent.emitter.on(event[:meta][:name]) do |received|
|
197
|
+
expect(received[:payload][:foo]).to eq("bar")
|
198
|
+
expect(received["payload"]["foo"]).to eq("bar")
|
199
|
+
end
|
200
|
+
|
201
|
+
subject
|
202
|
+
end
|
203
|
+
|
204
|
+
it "emits the item with name only and name with version" do
|
205
|
+
counter = 0
|
206
|
+
Hivent.emitter.on(event_name_with_version) do |_|
|
207
|
+
counter += 1
|
208
|
+
end
|
209
|
+
Hivent.emitter.on(event[:meta][:name]) do |_|
|
210
|
+
counter += 1
|
211
|
+
end
|
212
|
+
|
213
|
+
expect { subject }.to change { counter }.by(2)
|
214
|
+
end
|
215
|
+
|
216
|
+
it "removes the item from the queue" do
|
217
|
+
subject
|
218
|
+
|
219
|
+
expect(redis.llen("#{service_name}:0").to_i).to eq(0)
|
220
|
+
end
|
221
|
+
|
222
|
+
it "notifies life cycle event handler about the processed event" do
|
223
|
+
expect(life_cycle_event_handler).to receive(:event_processing_succeeded)
|
224
|
+
.with(event[:meta][:name], event[:meta][:version], event.with_indifferent_access)
|
225
|
+
subject
|
226
|
+
end
|
227
|
+
|
228
|
+
context "when several items are produced" do
|
229
|
+
let(:event2) { { foo: "bar" }.merge(event) }
|
230
|
+
|
231
|
+
before :each do
|
232
|
+
producer.write(event2[:meta][:name], event2.to_json, 0)
|
233
|
+
end
|
234
|
+
|
235
|
+
it "consumes all events in the order they were produced" do
|
236
|
+
events = []
|
237
|
+
Hivent.emitter.on(event_name_with_version) do |event|
|
238
|
+
events << event
|
239
|
+
end
|
240
|
+
|
241
|
+
2.times { consumer.consume }
|
242
|
+
|
243
|
+
expect(events).to eq([event, event2].map(&:with_indifferent_access))
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
context "when processing fails" do
|
248
|
+
|
249
|
+
let(:dead_letter_queue) { "#{service_name}:0:dead_letter" }
|
250
|
+
|
251
|
+
before :each do
|
252
|
+
Hivent.emitter.on(event_name_with_version) do |_|
|
253
|
+
raise "something went wrong!"
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
it "puts the item into a dead letter queue" do
|
258
|
+
expect { subject }.to change { redis.llen(dead_letter_queue) }.by(1)
|
259
|
+
end
|
260
|
+
|
261
|
+
it "notifies life cycle event handler about the error" do
|
262
|
+
expect(life_cycle_event_handler).to receive(:event_processing_failed)
|
263
|
+
.with(instance_of(RuntimeError), event.with_indifferent_access, event.to_json, dead_letter_queue)
|
264
|
+
subject
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
end
|
269
|
+
|
270
|
+
context "when there are no items ready to be consumed" do
|
271
|
+
before :each do
|
272
|
+
allow(Kernel).to receive(:sleep)
|
273
|
+
|
274
|
+
redis.ltrim("#{service_name}:0", 1, -1)
|
275
|
+
end
|
276
|
+
|
277
|
+
it "sleeps for a little while" do
|
278
|
+
subject
|
279
|
+
expect(Kernel).to have_received(:sleep).with(described_class::SLEEP_TIME.to_f / 1000)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
describe "Wildcard event consumption" do
|
284
|
+
before :each do
|
285
|
+
redis.set("#{service_name}:partition_count", partition_count)
|
286
|
+
redis.sadd("*", service_name)
|
287
|
+
|
288
|
+
producer.write("some_event_name", { foo: "bar", meta: { name: "some_event_name" } }.to_json, 0)
|
289
|
+
end
|
290
|
+
|
291
|
+
it "consumes all events" do
|
292
|
+
counter = 0
|
293
|
+
Hivent.emitter.on(Hivent::Emitter::WILDCARD) do |_|
|
294
|
+
counter += 1
|
295
|
+
end
|
296
|
+
|
297
|
+
expect { subject }.to change { counter }.by(1)
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
describe "#run!" do
|
303
|
+
subject { Thread.new { consumer.run! } }
|
304
|
+
|
305
|
+
let(:partition_count) { 2 }
|
306
|
+
|
307
|
+
before :each do
|
308
|
+
redis.set("#{service_name}:partition_count", partition_count)
|
309
|
+
|
310
|
+
allow(consumer).to receive(:consume)
|
311
|
+
end
|
312
|
+
|
313
|
+
it "processes items" do
|
314
|
+
thread = subject
|
315
|
+
|
316
|
+
sleep 0.1
|
317
|
+
|
318
|
+
thread.kill
|
319
|
+
|
320
|
+
expect(consumer).to have_received(:consume).at_least(:once)
|
321
|
+
end
|
322
|
+
|
323
|
+
end
|
324
|
+
|
325
|
+
describe "#stop!" do
|
326
|
+
|
327
|
+
let(:partition_count) { 2 }
|
328
|
+
|
329
|
+
before :each do
|
330
|
+
redis.set("#{service_name}:partition_count", partition_count)
|
331
|
+
end
|
332
|
+
|
333
|
+
it "stops processing" do
|
334
|
+
thread = Thread.new do
|
335
|
+
consumer.run!
|
336
|
+
end
|
337
|
+
|
338
|
+
sleep 0.1
|
339
|
+
|
340
|
+
consumer.stop!
|
341
|
+
|
342
|
+
# nil is returned if timeout expires
|
343
|
+
expect(thread.join(2)).to eq(thread)
|
344
|
+
end
|
345
|
+
|
346
|
+
end
|
347
|
+
|
348
|
+
end
|