service_skeleton 0.0.0.1.ENOTAG → 0.0.0.2.g46c1e0e

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +0 -2
  3. data/.rubocop.yml +114 -9
  4. data/.travis.yml +11 -0
  5. data/README.md +153 -279
  6. data/lib/service_skeleton/background_worker.rb +80 -0
  7. data/lib/service_skeleton/config.rb +18 -78
  8. data/lib/service_skeleton/config_variable.rb +8 -29
  9. data/lib/service_skeleton/config_variables.rb +68 -54
  10. data/lib/service_skeleton/error.rb +3 -5
  11. data/lib/service_skeleton/filtering_logger.rb +0 -2
  12. data/lib/service_skeleton/logging_helpers.rb +3 -10
  13. data/lib/service_skeleton/metrics_methods.rb +13 -28
  14. data/lib/service_skeleton/signal_handler.rb +183 -0
  15. data/lib/service_skeleton.rb +145 -22
  16. data/service_skeleton.gemspec +9 -10
  17. metadata +19 -102
  18. data/.editorconfig +0 -7
  19. data/.git-blame-ignore-revs +0 -2
  20. data/.github/workflows/ci.yml +0 -50
  21. data/lib/service_skeleton/config_class.rb +0 -16
  22. data/lib/service_skeleton/config_variable/boolean.rb +0 -21
  23. data/lib/service_skeleton/config_variable/enum.rb +0 -27
  24. data/lib/service_skeleton/config_variable/float.rb +0 -25
  25. data/lib/service_skeleton/config_variable/integer.rb +0 -25
  26. data/lib/service_skeleton/config_variable/kv_list.rb +0 -26
  27. data/lib/service_skeleton/config_variable/path_list.rb +0 -13
  28. data/lib/service_skeleton/config_variable/string.rb +0 -18
  29. data/lib/service_skeleton/config_variable/url.rb +0 -36
  30. data/lib/service_skeleton/config_variable/yaml_file.rb +0 -42
  31. data/lib/service_skeleton/generator.rb +0 -165
  32. data/lib/service_skeleton/metric_method_name.rb +0 -9
  33. data/lib/service_skeleton/runner.rb +0 -46
  34. data/lib/service_skeleton/service_name.rb +0 -20
  35. data/lib/service_skeleton/signal_manager.rb +0 -202
  36. data/lib/service_skeleton/signals_methods.rb +0 -15
  37. data/lib/service_skeleton/ultravisor_children.rb +0 -20
  38. data/lib/service_skeleton/ultravisor_loggerstash.rb +0 -11
  39. data/ultravisor/.yardopts +0 -1
  40. data/ultravisor/Guardfile +0 -9
  41. data/ultravisor/README.md +0 -404
  42. data/ultravisor/lib/ultravisor/child/call.rb +0 -21
  43. data/ultravisor/lib/ultravisor/child/call_receiver.rb +0 -14
  44. data/ultravisor/lib/ultravisor/child/cast.rb +0 -16
  45. data/ultravisor/lib/ultravisor/child/cast_receiver.rb +0 -11
  46. data/ultravisor/lib/ultravisor/child/process_cast_call.rb +0 -39
  47. data/ultravisor/lib/ultravisor/child.rb +0 -481
  48. data/ultravisor/lib/ultravisor/error.rb +0 -25
  49. data/ultravisor/lib/ultravisor/logging_helpers.rb +0 -32
  50. data/ultravisor/lib/ultravisor.rb +0 -216
  51. data/ultravisor/spec/example_group_methods.rb +0 -19
  52. data/ultravisor/spec/example_methods.rb +0 -8
  53. data/ultravisor/spec/spec_helper.rb +0 -52
  54. data/ultravisor/spec/ultravisor/add_child_spec.rb +0 -79
  55. data/ultravisor/spec/ultravisor/child/call_spec.rb +0 -121
  56. data/ultravisor/spec/ultravisor/child/cast_spec.rb +0 -111
  57. data/ultravisor/spec/ultravisor/child/id_spec.rb +0 -21
  58. data/ultravisor/spec/ultravisor/child/new_spec.rb +0 -152
  59. data/ultravisor/spec/ultravisor/child/restart_delay_spec.rb +0 -40
  60. data/ultravisor/spec/ultravisor/child/restart_spec.rb +0 -70
  61. data/ultravisor/spec/ultravisor/child/run_spec.rb +0 -95
  62. data/ultravisor/spec/ultravisor/child/shutdown_spec.rb +0 -124
  63. data/ultravisor/spec/ultravisor/child/spawn_spec.rb +0 -107
  64. data/ultravisor/spec/ultravisor/child/unsafe_instance_spec.rb +0 -55
  65. data/ultravisor/spec/ultravisor/child/wait_spec.rb +0 -32
  66. data/ultravisor/spec/ultravisor/new_spec.rb +0 -71
  67. data/ultravisor/spec/ultravisor/remove_child_spec.rb +0 -49
  68. data/ultravisor/spec/ultravisor/run_spec.rb +0 -334
  69. data/ultravisor/spec/ultravisor/shutdown_spec.rb +0 -106
@@ -1,216 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "logger"
4
-
5
- require_relative "./ultravisor/child"
6
- require_relative "./ultravisor/error"
7
- require_relative "./ultravisor/logging_helpers"
8
-
9
- # A super-dooOOOoooper supervisor.
10
- #
11
- class Ultravisor
12
- include LoggingHelpers
13
-
14
- def initialize(children: [], strategy: :one_for_one, logger: Logger.new("/dev/null"))
15
- @queue, @logger = Queue.new, logger
16
-
17
- @strategy = strategy
18
- validate_strategy
19
-
20
- @op_m, @op_cv = Mutex.new, ConditionVariable.new
21
- @running_thread = nil
22
-
23
- initialize_children(children)
24
- end
25
-
26
- def run
27
- logger.debug(logloc) { "called" }
28
-
29
- @op_m.synchronize do
30
- if @running_thread
31
- raise AlreadyRunningError,
32
- "This ultravisor is already running"
33
- end
34
-
35
- @queue.clear
36
- @running_thread = Thread.current
37
- Thread.current.name = "Ultravisor"
38
- end
39
-
40
- logger.debug(logloc) { "Going to start children #{@children.map(&:first).inspect}" }
41
- @children.each { |c| c.last.spawn(@queue) }
42
-
43
- process_events
44
-
45
- @op_m.synchronize do
46
- logger.debug(logloc) { "Shutdown time for #{@children.reverse.map(&:first).inspect}" }
47
- @children.reverse.each { |c| c.last.shutdown }
48
-
49
- @running_thread = nil
50
- @op_cv.broadcast
51
- end
52
-
53
- self
54
- end
55
-
56
- def shutdown(wait: true, force: false)
57
- @op_m.synchronize do
58
- return self unless @running_thread
59
- if force
60
- @children.reverse.each { |c| c.last.shutdown(force: true) }
61
- @running_thread.kill
62
- @running_thread = nil
63
- @op_cv.broadcast
64
- else
65
- @queue << :shutdown
66
- if wait
67
- @op_cv.wait(@op_m) while @running_thread
68
- end
69
- end
70
- end
71
- self
72
- end
73
-
74
- def [](id)
75
- @children.assoc(id)&.last
76
- end
77
-
78
- def add_child(**args)
79
- logger.debug(logloc) { "Adding child #{args[:id].inspect}" }
80
- args[:logger] ||= logger
81
-
82
- @op_m.synchronize do
83
- c = Ultravisor::Child.new(**args)
84
-
85
- if @children.assoc(c.id)
86
- raise DuplicateChildError,
87
- "Child with ID #{c.id.inspect} already exists"
88
- end
89
-
90
- @children << [c.id, c]
91
-
92
- if @running_thread
93
- logger.debug(logloc) { "Auto-starting new child #{args[:id].inspect}" }
94
- c.spawn(@queue)
95
- end
96
- end
97
- end
98
-
99
- def remove_child(id)
100
- logger.debug(logloc) { "Removing child #{id.inspect}" }
101
-
102
- @op_m.synchronize do
103
- c = @children.assoc(id)
104
-
105
- return nil if c.nil?
106
-
107
- @children.delete(c)
108
- if @running_thread
109
- logger.debug(logloc) { "Shutting down removed child #{id.inspect}" }
110
- c.last.shutdown
111
- end
112
- end
113
- end
114
-
115
- private
116
-
117
- def validate_strategy
118
- unless %i{one_for_one all_for_one rest_for_one}.include?(@strategy)
119
- raise ArgumentError,
120
- "Invalid strategy #{@strategy.inspect}"
121
- end
122
- end
123
-
124
- def initialize_children(children)
125
- unless children.is_a?(Array)
126
- raise ArgumentError,
127
- "children must be an Array"
128
- end
129
-
130
- @children = []
131
-
132
- children.each do |cfg|
133
- cfg[:logger] ||= logger
134
- c = Ultravisor::Child.new(**cfg)
135
- if @children.assoc(c.id)
136
- raise DuplicateChildError,
137
- "Duplicate child ID: #{c.id.inspect}"
138
- end
139
-
140
- @children << [c.id, c]
141
- end
142
- end
143
-
144
- def process_events
145
- loop do
146
- qe = @queue.pop
147
-
148
- case qe
149
- when Ultravisor::Child
150
- logger.debug(logloc) { "Received Ultravisor::Child queue entry for #{qe.id}" }
151
- @op_m.synchronize { child_exited(qe) }
152
- when :shutdown
153
- logger.debug(logloc) { "Received :shutdown queue entry" }
154
- break
155
- else
156
- logger.error(logloc) { "Unknown queue entry: #{qe.inspect}" }
157
- end
158
- end
159
- end
160
-
161
- def child_exited(child)
162
- if child.termination_exception
163
- log_exception(child.termination_exception, "Ultravisor::Child(#{child.id.inspect})") { "Thread terminated by unhandled exception" }
164
- end
165
-
166
- if @running_thread.nil?
167
- logger.debug(logloc) { "Child termination after shutdown" }
168
- # Child termination processed after we've shut down... nope
169
- return
170
- end
171
-
172
- begin
173
- return unless child.restart?
174
- rescue Ultravisor::BlownRestartPolicyError
175
- # Uh oh...
176
- logger.error(logloc) { "Child #{child.id} has exceeded its restart policy. Shutting down the Ultravisor." }
177
- @queue << :shutdown
178
- return
179
- end
180
-
181
- case @strategy
182
- when :all_for_one
183
- @children.reverse.each do |id, c|
184
- # Don't need to shut down the child that has caused all this mess
185
- next if child.id == id
186
-
187
- c.shutdown
188
- end
189
- when :rest_for_one
190
- @children.reverse.each do |id, c|
191
- # Don't go past the child that caused the problems
192
- break if child.id == id
193
-
194
- c.shutdown
195
- end
196
- end
197
-
198
- sleep child.restart_delay
199
-
200
- case @strategy
201
- when :all_for_one
202
- @children.each do |_, c|
203
- c.spawn(@queue)
204
- end
205
- when :rest_for_one
206
- s = false
207
- @children.each do |id, c|
208
- s = true if child.id == id
209
-
210
- c.spawn(@queue) if s
211
- end
212
- when :one_for_one
213
- child.spawn(@queue)
214
- end
215
- end
216
- end
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'logger'
4
-
5
- module ExampleGroupMethods
6
- def uses_logger
7
- let(:logger) { instance_double(Logger, 'mock') }
8
-
9
- before(:each) do
10
- allow(Logger).to receive(:new).and_return(logger)
11
- allow(logger).to receive(:debug).with(instance_of(String))
12
- allow(logger).to receive(:info).with(instance_of(String))
13
- allow(logger).to receive(:error) { |p, &m| puts "#{p}: #{m.call}" }
14
- allow(logger).to receive(:level=).with(Logger::INFO)
15
- allow(logger).to receive(:formatter=).with(an_instance_of(Proc))
16
- allow(logger).to receive(:kind_of?).with(Logger).and_return(true)
17
- end
18
- end
19
- end
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
- module ExampleMethods
3
- def tmptrace
4
- require "tracer"
5
- Tracer.add_filter { |event, file, line, id, binding, klass, *rest| klass.to_s =~ /Ultravisor/ }
6
- Tracer.on { yield }
7
- end
8
- end
@@ -1,52 +0,0 @@
1
- # frozen_string_literal: true
2
- require 'bundler'
3
- Bundler.setup(:default, :development)
4
- require 'rspec/core'
5
- require 'rspec/mocks'
6
-
7
- Thread.report_on_exception = false
8
-
9
- require 'simplecov'
10
- SimpleCov.start do
11
- add_filter('spec')
12
- end
13
-
14
- class ListIncompletelyCoveredFiles
15
- def format(result)
16
- incompletes = result.files.select { |f| f.covered_percent < 100 }
17
-
18
- unless incompletes.empty?
19
- puts
20
- puts "Files with incomplete test coverage:"
21
- incompletes.each do |f|
22
- printf " %2.01f%% %s\n", f.covered_percent, f.filename
23
- end
24
- puts; puts
25
- end
26
- end
27
- end
28
-
29
- SimpleCov.formatters = SimpleCov::Formatter::MultiFormatter.new([
30
- SimpleCov::Formatter::HTMLFormatter,
31
- ListIncompletelyCoveredFiles
32
- ])
33
-
34
- require_relative "./example_group_methods"
35
- require_relative "./example_methods"
36
-
37
- RSpec.configure do |config|
38
- config.extend ExampleGroupMethods
39
- config.include ExampleMethods
40
-
41
- config.order = :random
42
- config.fail_fast = !!ENV["RSPEC_CONFIG_FAIL_FAST"]
43
- config.full_backtrace = !!ENV["RSPEC_CONFIG_FULL_BACKTRACE"]
44
-
45
- config.expect_with :rspec do |c|
46
- c.syntax = :expect
47
- end
48
-
49
- config.mock_with :rspec do |mocks|
50
- mocks.verify_partial_doubles = true
51
- end
52
- end
@@ -1,79 +0,0 @@
1
- # frozen_string_literal: true
2
- require_relative "../spec_helper"
3
-
4
- require_relative "../../lib/ultravisor"
5
-
6
- describe Ultravisor do
7
- let(:args) { {} }
8
- let(:ultravisor) { Ultravisor.new(**args) }
9
- let!(:child) { Ultravisor::Child.new(id: :xtra, klass: Class, method: :to_s) }
10
-
11
- describe "#add_child" do
12
- before(:each) do
13
- allow(Ultravisor::Child).to receive(:new).and_return(child)
14
- end
15
-
16
- context "when the ultravisor isn't running" do
17
- it "creates a new Child object" do
18
- expect(Ultravisor::Child).to receive(:new).with(id: :xtra, klass: Class, method: :to_s, logger: instance_of(Logger))
19
-
20
- ultravisor.add_child(id: :xtra, klass: Class, method: :to_s)
21
- end
22
-
23
- it "registers the new child" do
24
- ultravisor.add_child(id: :xtra, klass: Class, method: :to_s)
25
-
26
- expect(ultravisor[:xtra]).to be_a(Ultravisor::Child)
27
- end
28
-
29
- it "explodes if a dupe child ID is used" do
30
- ultravisor.add_child(id: :xtra, klass: Class, method: :to_s)
31
-
32
- expect do
33
- ultravisor.add_child(id: :xtra, klass: Object, method: :to_s)
34
- end.to raise_error(Ultravisor::DuplicateChildError)
35
- end
36
-
37
- it "doesn't spawn a child thread" do
38
- expect(child).to_not receive(:spawn)
39
-
40
- ultravisor.add_child(id: :xtra, klass: Class, method: :to_s)
41
- end
42
- end
43
-
44
- context "while the ultravisor *is* running" do
45
- let(:mock_thread) { instance_double(Thread) }
46
-
47
- before(:each) do
48
- allow(child).to receive(:spawn)
49
- ultravisor.instance_variable_set(:@running_thread, mock_thread)
50
- end
51
-
52
- it "creates a new Child object" do
53
- expect(Ultravisor::Child).to receive(:new).with(id: :xtra, klass: Class, method: :to_s, logger: instance_of(Logger))
54
-
55
- ultravisor.add_child(id: :xtra, klass: Class, method: :to_s)
56
- end
57
-
58
- it "registers the new child" do
59
- ultravisor.add_child(id: :xtra, klass: Class, method: :to_s)
60
-
61
- expect(ultravisor[:xtra]).to be_a(Ultravisor::Child)
62
- end
63
-
64
- it "explodes if a dupe child ID is used" do
65
- ultravisor.add_child(id: :xtra, klass: Class, method: :to_s)
66
-
67
- expect do
68
- ultravisor.add_child(id: :xtra, klass: Object, method: :to_s)
69
- end.to raise_error(Ultravisor::DuplicateChildError)
70
- end
71
-
72
- it "spawns a child thread" do
73
- ultravisor.add_child(id: :xtra, klass: Class, method: :to_s)
74
-
75
- expect(child).to have_received(:spawn)
76
- end
77
- end
78
- end
79
- end
@@ -1,121 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../../spec_helper"
4
-
5
- require_relative "../../../lib/ultravisor/child"
6
- require_relative "../../../lib/ultravisor/error"
7
-
8
- class CastCallTest
9
- class FakeError < RuntimeError; end
10
-
11
- def run
12
- end
13
-
14
- def poke_processor
15
- process_castcall
16
- end
17
-
18
- def failing_method
19
- raise FakeError
20
- end
21
- end
22
-
23
- describe Ultravisor::Child do
24
- let(:child) { Ultravisor::Child.new(**args) }
25
- let(:instance) { child.__send__(:new_instance) }
26
-
27
- describe "#call" do
28
- context "without enable_castcall" do
29
- let(:args) do
30
- {
31
- id: :call_child,
32
- klass: CastCallTest,
33
- method: :run,
34
- }
35
- end
36
-
37
- it "does not accept calls to #call" do
38
- expect { child.call }.to raise_error(NoMethodError)
39
- end
40
- end
41
-
42
- context "with enable_castcall" do
43
- let(:args) do
44
- {
45
- id: :call_child,
46
- klass: CastCallTest,
47
- method: :run,
48
- enable_castcall: true,
49
- }
50
- end
51
-
52
- before(:each) do
53
- # Got to have an instance otherwise all hell breaks loose
54
- child.instance_variable_set(:@instance, instance)
55
-
56
- # So we can check if and when it's been called
57
- allow(instance).to receive(:to_s).and_call_original
58
- allow(instance).to receive(:failing_method).and_call_original
59
- end
60
-
61
- it "accepts calls to #call" do
62
- expect { child.call }.to_not raise_error
63
- end
64
-
65
- it "does not accept call calls to methods that do not exist on the worker object" do
66
- expect { child.call.flibbetygibbets }.to raise_error(NoMethodError)
67
- end
68
-
69
- it "does not accept call calls to private methods on the worker object" do
70
- expect { child.call.eval }.to raise_error(NoMethodError)
71
- end
72
-
73
- it "accepts calls to methods that exist on the worker object" do
74
- th = Thread.new { child.call.to_s }
75
- th.join(0.01)
76
- instance.poke_processor
77
- expect { th.value }.to_not raise_error
78
- end
79
-
80
- it "calls the instance method only when process_castcall is called" do
81
- th = Thread.new { child.call.to_s }
82
- th.join(0.001)
83
-
84
- # Thread should be ticking along, not dead
85
- expect(th.status).to eq("sleep")
86
- expect(instance).to_not have_received(:to_s)
87
- instance.poke_processor
88
- expect(instance).to have_received(:to_s)
89
- expect(th.value).to be_a(String)
90
- end
91
-
92
- it "raises a relevant error if the call itself causes an exception is dying" do
93
- th = Thread.new { child.call.failing_method }
94
- th.join(0.001)
95
-
96
- expect(instance).to_not have_received(:failing_method)
97
- expect { instance.poke_processor }.to raise_error(CastCallTest::FakeError)
98
- expect(instance).to have_received(:failing_method)
99
-
100
- expect { th.value }.to raise_error(Ultravisor::ChildRestartedError)
101
- end
102
-
103
- it "raises a relevant error if the instance is dying" do
104
- instance.instance_variable_get(:@ultravisor_child_castcall_queue).close
105
-
106
- expect { child.call.to_s }.to raise_error(Ultravisor::ChildRestartedError)
107
- end
108
-
109
- it "raises an error to all incomplete calls if the instance terminates" do
110
- th = Thread.new { child.call.to_s }
111
-
112
- th.join(0.001) until th.status == "sleep"
113
-
114
- expect(instance).to_not have_received(:to_s)
115
- child.instance_variable_get(:@spawn_m).synchronize { child.__send__(:termination_cleanup) }
116
-
117
- expect { th.value }.to raise_error(Ultravisor::ChildRestartedError)
118
- end
119
- end
120
- end
121
- end
@@ -1,111 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../../spec_helper"
4
-
5
- require_relative "../../../lib/ultravisor/child"
6
- require_relative "../../../lib/ultravisor/error"
7
-
8
- class CastCallTest
9
- def run
10
- end
11
-
12
- def poke_processor
13
- process_castcall
14
- end
15
- end
16
-
17
- describe Ultravisor::Child do
18
- let(:child) { Ultravisor::Child.new(**args) }
19
- let(:instance) { child.__send__(:new_instance) }
20
-
21
- describe "#cast" do
22
- context "without enable_castcall" do
23
- let(:args) do
24
- {
25
- id: :cast_child,
26
- klass: CastCallTest,
27
- method: :run,
28
- }
29
- end
30
-
31
- it "does not accept calls to #cast" do
32
- expect { child.cast }.to raise_error(NoMethodError)
33
- end
34
- end
35
-
36
- context "with enable_castcall" do
37
- let(:args) do
38
- {
39
- id: :cast_child,
40
- klass: CastCallTest,
41
- method: :run,
42
- enable_castcall: true,
43
- }
44
- end
45
-
46
- before(:each) do
47
- # Got to have an instance otherwise all hell breaks loose
48
- child.instance_variable_set(:@instance, instance)
49
-
50
- # So we can check if and when it's been called
51
- allow(instance).to receive(:to_s).and_call_original
52
- end
53
-
54
- it "accepts calls to #cast" do
55
- expect { child.cast }.to_not raise_error
56
- end
57
-
58
- it "does not accept cast calls to methods that do not exist on the worker object" do
59
- expect { child.cast.flibbetygibbets }.to raise_error(NoMethodError)
60
- end
61
-
62
- it "does not accept cast calls to private methods on the worker object" do
63
- expect { child.cast.eval }.to raise_error(NoMethodError)
64
- end
65
-
66
- it "accepts calls to methods that exist on the worker object" do
67
- expect { child.cast.to_s }.to_not raise_error
68
- end
69
-
70
- it "calls the instance method only when process_castcall is called" do
71
- child.cast.to_s
72
- expect(instance).to_not have_received(:to_s)
73
- instance.poke_processor
74
- expect(instance).to have_received(:to_s)
75
- end
76
-
77
- it "processes all the queued method calls" do
78
- child.cast.to_s
79
- child.cast.to_s
80
- child.cast.to_s
81
- child.cast.to_s
82
- child.cast.to_s
83
- expect(instance).to_not have_received(:to_s)
84
- instance.poke_processor
85
- expect(instance).to have_received(:to_s).exactly(5).times
86
- end
87
-
88
- let(:cc_fd) { instance.__send__(:castcall_fd) }
89
- it "marks the castcall_fd as readable only after cast is called" do
90
- expect(IO.select([cc_fd], nil, nil, 0)).to eq(nil)
91
-
92
- child.cast.to_s
93
-
94
- expect(IO.select([cc_fd], nil, nil, 0)).to eq([[cc_fd], [], []])
95
- end
96
-
97
- it "does not have a readable castcall_fd after process_castcall" do
98
- child.cast.to_s
99
- expect(IO.select([cc_fd], nil, nil, 0)).to eq([[cc_fd], [], []])
100
- instance.poke_processor
101
- expect(IO.select([cc_fd], nil, nil, 0)).to eq(nil)
102
- end
103
-
104
- it "does not explode if the instance is dying" do
105
- instance.instance_variable_get(:@ultravisor_child_castcall_queue).close
106
-
107
- expect { child.cast.to_s }.to_not raise_error
108
- end
109
- end
110
- end
111
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../../spec_helper"
4
-
5
- require_relative "../../../lib/ultravisor/child"
6
- require_relative "../../../lib/ultravisor/error"
7
-
8
- describe Ultravisor::Child do
9
- let(:child) { Ultravisor::Child.new(**args) }
10
- let(:mock_class) { Class.new.tap { |k| k.class_eval { def run; end } } }
11
-
12
- describe "#id" do
13
- context "with minimal arguments" do
14
- let(:args) { { id: :bob, klass: mock_class, method: :run } }
15
-
16
- it "returns the child's ID" do
17
- expect(child.id).to eq(:bob)
18
- end
19
- end
20
- end
21
- end