hivent 1.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.
- 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,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Hivent::Redis::Signal do
|
5
|
+
|
6
|
+
let(:signal) do
|
7
|
+
Hivent::Redis::Signal.new(name)
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:name) { "my_topic" }
|
11
|
+
let(:client_id) { "client_id" }
|
12
|
+
let(:version) { 5 }
|
13
|
+
|
14
|
+
before :each do
|
15
|
+
Hivent.configure do |config|
|
16
|
+
config.backend = :redis
|
17
|
+
config.client_id = client_id
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "#emit" do
|
22
|
+
|
23
|
+
subject { signal.emit(payload, version: version, cid: cid, key: key) }
|
24
|
+
|
25
|
+
let(:payload) { { key: "value" } }
|
26
|
+
let(:cid) { nil }
|
27
|
+
let(:key) { SecureRandom.hex }
|
28
|
+
let(:redis_client) { Redis.new(url: REDIS_URL) }
|
29
|
+
let(:producer) { Hivent::Redis::Producer.new(redis_client) }
|
30
|
+
|
31
|
+
before :each do
|
32
|
+
allow(Redis).to receive(:new).and_return(redis_client)
|
33
|
+
allow(Hivent::Redis::Producer).to receive(:new).with(redis_client).and_return(producer)
|
34
|
+
end
|
35
|
+
|
36
|
+
after :each do
|
37
|
+
redis_client.flushall
|
38
|
+
end
|
39
|
+
|
40
|
+
context "when no consuming services are configured" do
|
41
|
+
it "does not send the message" do
|
42
|
+
expect { subject }.not_to change { redis_client.keys }
|
43
|
+
end
|
44
|
+
|
45
|
+
it "does not raise an error" do
|
46
|
+
expect { subject }.not_to raise_error
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "when an exception is raised" do
|
51
|
+
let(:max_tries) { 2 }
|
52
|
+
let(:wait_period) { 0.5 }
|
53
|
+
|
54
|
+
before :each do
|
55
|
+
stub_const("Hivent::Redis::Signal::MAX_TRIES", max_tries)
|
56
|
+
stub_const("Hivent::Redis::Signal::SLEEP", wait_period)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "retries sending the message after a wait period" do
|
60
|
+
tries = 0
|
61
|
+
|
62
|
+
expect(producer).to receive(:write).exactly(max_tries).times do |_message|
|
63
|
+
tries += 1
|
64
|
+
|
65
|
+
if tries < max_tries
|
66
|
+
raise Redis::CommandError.new
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
subject
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context "when a consuming service is configured" do
|
75
|
+
let(:service_name) { "some_service" }
|
76
|
+
let(:partition_count) { 1 }
|
77
|
+
|
78
|
+
before :each do
|
79
|
+
redis_client.sadd(name, service_name)
|
80
|
+
redis_client.set("#{service_name}:partition_count", partition_count)
|
81
|
+
end
|
82
|
+
|
83
|
+
it "creates a list containing the message" do
|
84
|
+
subject
|
85
|
+
item = JSON.parse(redis_client.lindex("#{service_name}:0", -1))
|
86
|
+
|
87
|
+
expect(item["payload"]).to eq(payload.with_indifferent_access)
|
88
|
+
end
|
89
|
+
|
90
|
+
context "when configured with more than one partition" do
|
91
|
+
let(:partition_count) { 2 }
|
92
|
+
|
93
|
+
it "creates a message in only one of the partitions" do
|
94
|
+
subject
|
95
|
+
items = Array.new(partition_count) do |n|
|
96
|
+
redis_client.lindex("#{service_name}:#{n}", -1)
|
97
|
+
end
|
98
|
+
|
99
|
+
expect(items.compact.length).to eq(1)
|
100
|
+
end
|
101
|
+
|
102
|
+
describe "message distribution" do
|
103
|
+
subject do
|
104
|
+
repetitions.times do
|
105
|
+
signal.emit(payload, version: version, cid: cid, key: SecureRandom.hex)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
let(:partition_count) { 4 }
|
110
|
+
let(:repetitions) { 1000 }
|
111
|
+
let(:lists) do
|
112
|
+
Array.new(partition_count) { |n| "#{service_name}:#{n}" }
|
113
|
+
end
|
114
|
+
let(:item_counts) do
|
115
|
+
lists.map { |list| redis_client.llen(list) }
|
116
|
+
end
|
117
|
+
let(:sum) { item_counts.sum }
|
118
|
+
|
119
|
+
it "publishes messages on all partitions" do
|
120
|
+
subject
|
121
|
+
expect(item_counts.all? { |count| count > 0 }).to be(true)
|
122
|
+
end
|
123
|
+
|
124
|
+
it "publishes all the messages emitted" do
|
125
|
+
subject
|
126
|
+
expect(sum).to be(repetitions)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
context "when multiple consuming services are configured" do
|
134
|
+
let(:service_names) { ["some_service", "other_service"] }
|
135
|
+
let(:partition_count) { 1 }
|
136
|
+
|
137
|
+
before :each do
|
138
|
+
service_names.each do |service_name|
|
139
|
+
redis_client.sadd(name, service_name)
|
140
|
+
redis_client.set("#{service_name}:partition_count", partition_count)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
it "creates a list for each service containing the message" do
|
145
|
+
subject
|
146
|
+
payloads = service_names.map do |service_name|
|
147
|
+
JSON.parse(redis_client.lindex("#{service_name}:0", -1))["payload"]
|
148
|
+
end
|
149
|
+
|
150
|
+
expect(payloads.uniq).to eq([payload.with_indifferent_access])
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
data/spec/hivent_spec.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Hivent do
|
5
|
+
|
6
|
+
let(:client_id) { "client_id" }
|
7
|
+
|
8
|
+
describe "Configuration" do
|
9
|
+
|
10
|
+
context "when configuring with an unsupported backend" do
|
11
|
+
it "raises an UnsupportedOption Error" do
|
12
|
+
expect {
|
13
|
+
Hivent.configure do |config|
|
14
|
+
config.backend = :unsupported
|
15
|
+
end
|
16
|
+
}.to raise_error(
|
17
|
+
Hivent::Config::Options::UnsupportedOptionError,
|
18
|
+
"Unsupported value :unsupported for option :backend"
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "when configuring with an empty client_id" do
|
24
|
+
it "raises an UnsupportedOption Error" do
|
25
|
+
expect {
|
26
|
+
Hivent.configure do |config|
|
27
|
+
config.client_id = nil
|
28
|
+
end
|
29
|
+
}.to raise_error(
|
30
|
+
Hivent::Config::Options::UnsupportedOptionError,
|
31
|
+
"Unsupported value nil for option :client_id"
|
32
|
+
)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "when configuring with a non-integer partition_count" do
|
37
|
+
it "raises an UnsupportedOption Error" do
|
38
|
+
expect {
|
39
|
+
Hivent.configure do |config|
|
40
|
+
config.partition_count = "foo"
|
41
|
+
end
|
42
|
+
}.to raise_error(
|
43
|
+
Hivent::Config::Options::UnsupportedOptionError,
|
44
|
+
"Unsupported value \"foo\" for option :partition_count"
|
45
|
+
)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context "when configuring with a partition_count smaller than 1" do
|
50
|
+
it "raises an UnsupportedOption Error" do
|
51
|
+
expect {
|
52
|
+
Hivent.configure do |config|
|
53
|
+
config.partition_count = 0
|
54
|
+
end
|
55
|
+
}.to raise_error(
|
56
|
+
Hivent::Config::Options::UnsupportedOptionError,
|
57
|
+
"Unsupported value 0 for option :partition_count"
|
58
|
+
)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "accessing Configured values" do
|
63
|
+
|
64
|
+
before :each do
|
65
|
+
Hivent.configure do |config|
|
66
|
+
config.client_id = client_id
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
subject { Hivent::Config }
|
71
|
+
|
72
|
+
let(:client_id) { "an_id" }
|
73
|
+
|
74
|
+
its(:client_id) { is_expected.to eq(client_id) }
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "Event consumption" do
|
81
|
+
subject { Hivent.emitter.emit "my_signal:1", increase: increase }
|
82
|
+
|
83
|
+
let(:increase) { 5 }
|
84
|
+
|
85
|
+
after :each do
|
86
|
+
Hivent.emitter.remove_listener "my_signal:1"
|
87
|
+
end
|
88
|
+
|
89
|
+
it "consumes events" do
|
90
|
+
counter = 0
|
91
|
+
Hivent.emitter.on "my_signal:1" do |data|
|
92
|
+
counter += data[:increase]
|
93
|
+
end
|
94
|
+
|
95
|
+
expect { subject }.to change { counter }.by(increase)
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Hivent::Spec::Matchers do
|
5
|
+
|
6
|
+
subject do
|
7
|
+
Hivent::Spec::Signal.new(signal).emit(payload, version: version, cid: cid)
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:signal) { 'my:signal' }
|
11
|
+
let(:payload) { { foo: 'bar' } }
|
12
|
+
let(:version) { 1 }
|
13
|
+
let(:cid) { SecureRandom.hex }
|
14
|
+
|
15
|
+
describe '#emit' do
|
16
|
+
|
17
|
+
it 'checks if an event was emitted' do
|
18
|
+
expect { subject }.to emit(signal)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'checks if an event was emitted with a given version' do
|
22
|
+
expect { subject }.to emit(signal, version: version)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'checks if an event was emitted with a given cid' do
|
26
|
+
expect { subject }.to emit(signal, cid: cid)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'checks if an event was not emitted' do
|
30
|
+
expect { subject }.not_to emit('other:signal')
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'checks if an event was not emitted with a given version' do
|
34
|
+
expect { subject }.not_to emit(signal, version: 2)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'checks if an event was not emitted with a given cid' do
|
38
|
+
expect { subject }.not_to emit(signal, cid: 'does-not-match')
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'checks if an event was emitted with a given payload' do
|
42
|
+
expect { subject }.to emit(signal).with(payload)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'checks if an event was not emitted with a given payload' do
|
46
|
+
expect { subject }.not_to emit(signal).with(bar: 'baz')
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'when the subject emits multiple signals' do
|
50
|
+
subject do
|
51
|
+
Hivent::Spec::Signal.new(signal).emit({ bar: 'baz' }, version: version)
|
52
|
+
Hivent::Spec::Signal.new(signal).emit(payload, version: version)
|
53
|
+
Hivent::Spec::Signal.new(signal).emit({ foo: 'other' }, version: version)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'checks if an event was emitted' do
|
57
|
+
expect { subject }.to emit(signal)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'checks if an event was emitted with a given payload' do
|
61
|
+
expect { subject }.to emit(signal).with(payload)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Hivent::Spec::Signal do
|
5
|
+
subject do
|
6
|
+
described_class.new(name)
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:name) { "my:signal" }
|
10
|
+
|
11
|
+
describe "#emit" do
|
12
|
+
subject { super().emit(payload, version: version) }
|
13
|
+
|
14
|
+
let(:payload) { { foo: "bar" } }
|
15
|
+
let(:version) { 1 }
|
16
|
+
|
17
|
+
before :each do
|
18
|
+
allow(Hivent.emitter).to receive(:emit)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "emits that event on the wildcard channel" do
|
22
|
+
expect(Hivent.emitter).to receive(:emit)
|
23
|
+
.with(Hivent::Emitter::WILDCARD, hash_including(payload: payload))
|
24
|
+
|
25
|
+
subject
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'emits that event with the given name' do
|
29
|
+
expect(Hivent.emitter).to receive(:emit).with(name, hash_including(payload: payload))
|
30
|
+
|
31
|
+
subject
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'emits that event with the given name and version' do
|
35
|
+
expect(Hivent.emitter).to receive(:emit).with("#{name}:#{version}", hash_including(payload: payload))
|
36
|
+
|
37
|
+
subject
|
38
|
+
end
|
39
|
+
|
40
|
+
context "with a Redis backend" do
|
41
|
+
before :each do
|
42
|
+
Hivent.configure do |config|
|
43
|
+
config.backend = :redis
|
44
|
+
config.life_cycle_event_handler = life_cycle_event_handler
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
let(:life_cycle_event_handler) { double }
|
49
|
+
|
50
|
+
it "notifies life cycle event handler about the processed event" do
|
51
|
+
expect(life_cycle_event_handler).to receive(:event_processing_succeeded)
|
52
|
+
.with(name, version, hash_including(payload: payload))
|
53
|
+
subject
|
54
|
+
end
|
55
|
+
|
56
|
+
context "when emitting fails" do
|
57
|
+
|
58
|
+
let(:error) { StandardError.new("error") }
|
59
|
+
|
60
|
+
before :each do
|
61
|
+
allow(Hivent.emitter).to receive(:emit).and_raise(error)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "notifies life cycle event handler about the error" do
|
65
|
+
expect(life_cycle_event_handler).to receive(:event_processing_failed)
|
66
|
+
.with(error, hash_including(payload: payload), kind_of(String), kind_of(String))
|
67
|
+
subject
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'simplecov'
|
3
|
+
|
4
|
+
require 'rspec/its'
|
5
|
+
require 'pry'
|
6
|
+
|
7
|
+
require 'hivent'
|
8
|
+
require 'hivent/rspec'
|
9
|
+
require 'hivent/cli/runner'
|
10
|
+
|
11
|
+
if ENV['CODECLIMATE_REPO_TOKEN']
|
12
|
+
require 'codeclimate-test-reporter'
|
13
|
+
CodeClimate::TestReporter.start
|
14
|
+
end
|
15
|
+
|
16
|
+
Dir.glob("#{File.expand_path(File.dirname(__FILE__))}/support/**/*.rb").each { |f| require f }
|
17
|
+
|
18
|
+
REDIS_URL = ENV["REDIS_URL"].presence || "redis://localhost:6379/15"
|
19
|
+
|
20
|
+
RSpec.configure do |config|
|
21
|
+
config.include STDOUTHelpers
|
22
|
+
|
23
|
+
config.after :each do
|
24
|
+
Hivent.configure {}
|
25
|
+
Hivent::Redis.class_variable_set(:@@redis, nil)
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
RSpec::Matchers.define :exit_with_code do |exp_code|
|
3
|
+
actual = nil
|
4
|
+
|
5
|
+
match do |block|
|
6
|
+
begin
|
7
|
+
block.call
|
8
|
+
rescue SystemExit => e
|
9
|
+
actual = e.status
|
10
|
+
end
|
11
|
+
actual && actual == exp_code
|
12
|
+
end
|
13
|
+
|
14
|
+
failure_message do
|
15
|
+
"expected block to call exit(#{exp_code}) but exit" +
|
16
|
+
(actual.nil? ? " not called" : "(#{actual}) was called")
|
17
|
+
end
|
18
|
+
|
19
|
+
failure_message_when_negated do
|
20
|
+
"expected block not to call exit(#{exp_code})"
|
21
|
+
end
|
22
|
+
|
23
|
+
description do
|
24
|
+
"expect block to call exit(#{exp_code})"
|
25
|
+
end
|
26
|
+
|
27
|
+
supports_block_expectations
|
28
|
+
end
|