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.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +19 -0
  3. data/.gitignore +14 -0
  4. data/.rspec +1 -0
  5. data/.rubocop.yml +1063 -0
  6. data/.ruby-version +1 -0
  7. data/.simplecov.template +1 -0
  8. data/.travis.yml +23 -0
  9. data/.version +1 -0
  10. data/Gemfile +4 -0
  11. data/LICENSE +21 -0
  12. data/README.md +196 -0
  13. data/bin/hivent +5 -0
  14. data/hivent.gemspec +34 -0
  15. data/lib/hivent.rb +32 -0
  16. data/lib/hivent/abstract_signal.rb +63 -0
  17. data/lib/hivent/cli/consumer.rb +60 -0
  18. data/lib/hivent/cli/runner.rb +50 -0
  19. data/lib/hivent/cli/start_option_parser.rb +53 -0
  20. data/lib/hivent/config.rb +22 -0
  21. data/lib/hivent/config/options.rb +51 -0
  22. data/lib/hivent/emitter.rb +41 -0
  23. data/lib/hivent/life_cycle_event_handler.rb +41 -0
  24. data/lib/hivent/redis/consumer.rb +82 -0
  25. data/lib/hivent/redis/extensions.rb +26 -0
  26. data/lib/hivent/redis/lua/consumer.lua +179 -0
  27. data/lib/hivent/redis/lua/producer.lua +27 -0
  28. data/lib/hivent/redis/producer.rb +24 -0
  29. data/lib/hivent/redis/redis.rb +14 -0
  30. data/lib/hivent/redis/signal.rb +36 -0
  31. data/lib/hivent/rspec.rb +11 -0
  32. data/lib/hivent/signal.rb +14 -0
  33. data/lib/hivent/spec.rb +11 -0
  34. data/lib/hivent/spec/matchers.rb +14 -0
  35. data/lib/hivent/spec/matchers/emit.rb +116 -0
  36. data/lib/hivent/spec/signal.rb +60 -0
  37. data/lib/hivent/version.rb +6 -0
  38. data/spec/codeclimate_helper.rb +5 -0
  39. data/spec/fixtures/cli/bootstrap_consumers.rb +7 -0
  40. data/spec/fixtures/cli/life_cycle_event_test.rb +8 -0
  41. data/spec/hivent/abstract_signal_spec.rb +161 -0
  42. data/spec/hivent/cli/consumer_spec.rb +68 -0
  43. data/spec/hivent/cli/runner_spec.rb +75 -0
  44. data/spec/hivent/cli/start_option_parser_spec.rb +48 -0
  45. data/spec/hivent/life_cycle_event_handler_spec.rb +38 -0
  46. data/spec/hivent/redis/consumer_spec.rb +348 -0
  47. data/spec/hivent/redis/signal_spec.rb +155 -0
  48. data/spec/hivent_spec.rb +100 -0
  49. data/spec/spec/matchers/emit_spec.rb +66 -0
  50. data/spec/spec/signal_spec.rb +72 -0
  51. data/spec/spec_helper.rb +27 -0
  52. data/spec/support/matchers/exit_with_code.rb +28 -0
  53. data/spec/support/stdout_helpers.rb +25 -0
  54. 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
@@ -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
@@ -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