service_skeleton 1.0.3 → 1.0.4

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/.git-blame-ignore-revs +2 -0
  3. data/.github/workflows/ci.yml +16 -0
  4. data/.rubocop.yml +2 -0
  5. data/lib/service_skeleton/generator.rb +1 -1
  6. data/lib/service_skeleton/runner.rb +1 -1
  7. data/service_skeleton.gemspec +0 -1
  8. data/ultravisor/.yardopts +1 -0
  9. data/ultravisor/Guardfile +9 -0
  10. data/ultravisor/README.md +404 -0
  11. data/ultravisor/lib/ultravisor.rb +216 -0
  12. data/ultravisor/lib/ultravisor/child.rb +481 -0
  13. data/ultravisor/lib/ultravisor/child/call.rb +21 -0
  14. data/ultravisor/lib/ultravisor/child/call_receiver.rb +14 -0
  15. data/ultravisor/lib/ultravisor/child/cast.rb +16 -0
  16. data/ultravisor/lib/ultravisor/child/cast_receiver.rb +11 -0
  17. data/ultravisor/lib/ultravisor/child/process_cast_call.rb +39 -0
  18. data/ultravisor/lib/ultravisor/error.rb +25 -0
  19. data/ultravisor/lib/ultravisor/logging_helpers.rb +32 -0
  20. data/ultravisor/spec/example_group_methods.rb +19 -0
  21. data/ultravisor/spec/example_methods.rb +8 -0
  22. data/ultravisor/spec/spec_helper.rb +52 -0
  23. data/ultravisor/spec/ultravisor/add_child_spec.rb +79 -0
  24. data/ultravisor/spec/ultravisor/child/call_spec.rb +121 -0
  25. data/ultravisor/spec/ultravisor/child/cast_spec.rb +111 -0
  26. data/ultravisor/spec/ultravisor/child/id_spec.rb +21 -0
  27. data/ultravisor/spec/ultravisor/child/new_spec.rb +152 -0
  28. data/ultravisor/spec/ultravisor/child/restart_delay_spec.rb +40 -0
  29. data/ultravisor/spec/ultravisor/child/restart_spec.rb +70 -0
  30. data/ultravisor/spec/ultravisor/child/run_spec.rb +95 -0
  31. data/ultravisor/spec/ultravisor/child/shutdown_spec.rb +124 -0
  32. data/ultravisor/spec/ultravisor/child/spawn_spec.rb +107 -0
  33. data/ultravisor/spec/ultravisor/child/unsafe_instance_spec.rb +55 -0
  34. data/ultravisor/spec/ultravisor/child/wait_spec.rb +32 -0
  35. data/ultravisor/spec/ultravisor/new_spec.rb +71 -0
  36. data/ultravisor/spec/ultravisor/remove_child_spec.rb +49 -0
  37. data/ultravisor/spec/ultravisor/run_spec.rb +334 -0
  38. data/ultravisor/spec/ultravisor/shutdown_spec.rb +106 -0
  39. metadata +34 -16
@@ -0,0 +1,124 @@
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
+ uses_logger
10
+
11
+ let(:base_args) { { id: :bob, klass: mock_class, method: :run } }
12
+ let(:child) { Ultravisor::Child.new(**args) }
13
+ let(:mock_class) { Class.new.tap { |k| k.class_eval { def run; end; def sigterm; end } } }
14
+ let(:mock_instance) { instance_double(mock_class) }
15
+ let(:term_queue) { instance_double(Queue) }
16
+
17
+ describe "#shutdown" do
18
+ let(:args) { base_args }
19
+
20
+ context "when the child isn't running" do
21
+ it "returns immediately" do
22
+ child.shutdown
23
+ end
24
+ end
25
+
26
+ context "when the child is running" do
27
+ before(:each) do
28
+ orig_thread_new = Thread.method(:new)
29
+ allow(Thread).to receive(:new) do |&b|
30
+ orig_thread_new.call(&b).tap do |th|
31
+ @thread = th
32
+ allow(th).to receive(:kill).and_call_original
33
+ allow(th).to receive(:join).and_call_original
34
+ end
35
+ end
36
+
37
+ allow(mock_class).to receive(:new).and_return(mock_instance)
38
+ allow(mock_instance).to receive(:run)
39
+
40
+ allow(term_queue).to receive(:<<)
41
+ end
42
+
43
+ it "kills the thread" do
44
+ child.spawn(term_queue).shutdown
45
+
46
+ expect(@thread).to have_received(:kill)
47
+ end
48
+
49
+ it "waits for the thread to be done" do
50
+ child.spawn(term_queue).shutdown
51
+
52
+ expect(@thread).to have_received(:join).with(1)
53
+ end
54
+
55
+ it "doesn't put anything on the queue" do
56
+ expect(term_queue).to_not receive(:<<)
57
+
58
+ child.spawn(term_queue).shutdown
59
+ end
60
+
61
+ context "when there's a shutdown spec" do
62
+ let(:args) { base_args.merge(shutdown: { method: :sigterm, timeout: 0.05 }) }
63
+
64
+ before(:each) do
65
+ allow(mock_instance).to receive(:sigterm)
66
+ end
67
+
68
+ it "calls the specified shutdown method" do
69
+ expect(mock_instance).to receive(:sigterm)
70
+
71
+ child.spawn(term_queue).shutdown
72
+ end
73
+
74
+ it "waits for up to the timeout period" do
75
+ child.spawn(term_queue).shutdown
76
+
77
+ expect(@thread).to have_received(:join).with(0.05)
78
+ end
79
+
80
+ context "the worker doesn't finish quickly enough" do
81
+ before(:each) do
82
+ allow(mock_instance).to receive(:run) { sleep 15 }
83
+ end
84
+
85
+ it "kills the thread" do
86
+ child.spawn(term_queue).shutdown
87
+
88
+ expect(@thread).to have_received(:kill)
89
+ end
90
+ end
91
+ end
92
+
93
+ context "when the thread infinihangs" do
94
+ # No need for a big timeout, we know it's not going to succeed
95
+ let(:args) { base_args.merge(shutdown: { timeout: 0.000001 }) }
96
+ let(:m) { Mutex.new }
97
+ let(:cv) { ConditionVariable.new }
98
+
99
+ before(:each) do
100
+ allow(mock_instance).to receive(:run) do
101
+ Thread.handle_interrupt(Numeric => :never) do
102
+ m.synchronize do
103
+ @state = 1
104
+ cv.signal
105
+ cv.wait(m) until @state == 2
106
+ end
107
+ end
108
+ end
109
+
110
+ allow(logger).to receive(:error)
111
+ end
112
+
113
+ it "logs an error" do
114
+ expect(logger).to receive(:error)
115
+
116
+ child.spawn(term_queue)
117
+ m.synchronize { cv.wait(m) until @state == 1 }
118
+ child.shutdown
119
+ m.synchronize { @state = 2; cv.signal }
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,107 @@
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
+ let(:mock_instance) { double(mock_class) } # rubocop:disable RSpec/VerifiedDoubles
12
+ let(:term_queue) { instance_double(Queue) }
13
+
14
+ describe "#spawn" do
15
+ before(:each) do
16
+ allow(term_queue).to receive(:<<)
17
+ end
18
+
19
+ context "with minimal arguments" do
20
+ let(:args) { { id: :bob, klass: mock_class, method: :run } }
21
+
22
+ before(:each) do
23
+ allow(mock_class).to receive(:new).and_return(mock_instance)
24
+ allow(mock_instance).to receive(:run)
25
+ end
26
+
27
+ it "instantiates the class" do
28
+ expect(mock_class).to receive(:new).with(no_args)
29
+
30
+ child.spawn(term_queue).wait
31
+ end
32
+
33
+ it "calls the run method on the class instance" do
34
+ expect(mock_instance).to receive(:run).with(no_args)
35
+
36
+ child.spawn(term_queue).wait
37
+ end
38
+
39
+ it "registers the thread it is running in" do
40
+ expect(mock_instance).to receive(:run) do
41
+ expect(child.instance_variable_get(:@thread)).to eq(Thread.current)
42
+ end
43
+
44
+ child.spawn(term_queue).wait
45
+ end
46
+
47
+ it "notes the start time" do
48
+ expect(mock_instance).to receive(:run) do
49
+ # Can only check @start_time while the child is running, as the
50
+ # variable gets nil'd after the run completes
51
+ expect(child.instance_variable_get(:@start_time).to_f).to be_within(0.01).of(Time.now.to_f)
52
+ end
53
+
54
+ child.spawn(term_queue).wait
55
+ end
56
+
57
+ it "notes the termination value" do
58
+ expect(mock_instance).to receive(:run).with(no_args).and_return(42)
59
+
60
+ child.spawn(term_queue)
61
+
62
+ expect(child.termination_value).to eq(42)
63
+ end
64
+
65
+ it "tells the ultravisor it terminated" do
66
+ expect(term_queue).to receive(:<<).with(child)
67
+
68
+ child.spawn(term_queue).wait
69
+ end
70
+
71
+ it "creates a new thread" do
72
+ expect(Thread).to receive(:new)
73
+
74
+ child.spawn(term_queue).wait
75
+ end
76
+
77
+ context "when the worker object's run method raises an exception" do
78
+ before(:each) do
79
+ allow(mock_instance).to receive(:run).and_raise(RuntimeError.new("FWACKOOM"))
80
+ end
81
+
82
+ it "makes a note of the exception" do
83
+ child.spawn(term_queue)
84
+
85
+ expect(child.termination_exception).to be_a(RuntimeError)
86
+ end
87
+
88
+ it "tells the ultravisor it terminated" do
89
+ expect(term_queue).to receive(:<<).with(child)
90
+
91
+ child.spawn(term_queue).wait
92
+ end
93
+ end
94
+ end
95
+
96
+ context "with a worker class that takes args" do
97
+ let(:args) { { id: :testy, klass: mock_class, args: ["foo", "bar", baz: "wombat"], method: :run } }
98
+ let(:mock_class) { Class.new.tap { |k| k.class_eval { def initialize(*x); end; def run; end } } }
99
+
100
+ it "creates the class instance with args" do
101
+ expect(mock_class).to receive(:new).with("foo", "bar", baz: "wombat").and_call_original
102
+
103
+ child.spawn(term_queue).wait
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,55 @@
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(:args) { { id: :bob, klass: Object, method: :to_s } }
10
+ let(:child) { Ultravisor::Child.new(**args) }
11
+
12
+ describe "#unsafe_instance" do
13
+ context "by default" do
14
+ it "explodes" do
15
+ expect { child.unsafe_instance }.to raise_error(Ultravisor::ThreadSafetyError)
16
+ end
17
+ end
18
+
19
+ context "with access: :unsafe" do
20
+ let(:args) { { id: :bob, klass: Object, method: :to_s, access: :unsafe } }
21
+
22
+ context "when there's no instance object" do
23
+ it "waits for the instance object to appear" do
24
+ expect(child.instance_variable_get(:@spawn_cv)).to receive(:wait) do
25
+ child.instance_variable_set(:@instance, "gogogo")
26
+ end
27
+
28
+ child.unsafe_instance
29
+ end
30
+ end
31
+
32
+ context "when there's an instance object" do
33
+ before(:each) do
34
+ child.instance_variable_set(:@instance, "bob")
35
+ end
36
+
37
+ it "returns the instance object" do
38
+ expect(child.unsafe_instance).to eq("bob")
39
+ end
40
+ end
41
+ end
42
+
43
+ context "when the child is running" do
44
+ it "only exits once the child has finished" do
45
+ child.instance_variable_set(:@thread, Thread.new {})
46
+
47
+ expect(child.instance_variable_get(:@spawn_cv)).to receive(:wait).with(child.instance_variable_get(:@spawn_m)) do
48
+ child.instance_variable_set(:@thread, nil)
49
+ end
50
+
51
+ child.wait
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,32 @@
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(:args) { { id: :bob, klass: mock_class, method: :run } }
10
+ let(:child) { Ultravisor::Child.new(**args) }
11
+ let(:mock_class) { Class.new.tap { |k| k.class_eval { def run; end } } }
12
+
13
+ describe "#wait" do
14
+ context "when the child isn't running" do
15
+ it "just returns straight away" do
16
+ child.wait
17
+ end
18
+ end
19
+
20
+ context "when the child is running" do
21
+ it "only exits once the child has finished" do
22
+ child.instance_variable_set(:@thread, Thread.new {})
23
+
24
+ expect(child.instance_variable_get(:@spawn_cv)).to receive(:wait).with(child.instance_variable_get(:@spawn_m)) do
25
+ child.instance_variable_set(:@thread, nil)
26
+ end
27
+
28
+ child.wait
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+ require_relative "../spec_helper"
3
+
4
+ require_relative "../../lib/ultravisor"
5
+
6
+ describe Ultravisor do
7
+ describe ".new" do
8
+ context "without arguments" do
9
+ it "does not explode" do
10
+ expect { Ultravisor.new }.to_not raise_error
11
+ end
12
+
13
+ it "gives us an Ultravisor instance" do
14
+ expect(Ultravisor.new).to be_a(Ultravisor)
15
+ end
16
+ end
17
+
18
+ context "with empty children" do
19
+ it "does not explode" do
20
+ expect { Ultravisor.new children: [] }.to_not raise_error
21
+ end
22
+ end
23
+
24
+ context "with children that isn't an array" do
25
+ it "raises an error" do
26
+ [{}, "ohai!", nil, 42].each do |v|
27
+ expect { Ultravisor.new children: v }.to raise_error(ArgumentError)
28
+ end
29
+ end
30
+ end
31
+
32
+ context "with valid children" do
33
+ let(:ultravisor) { Ultravisor.new(children: [{ id: :testy, klass: Object, method: :to_s }]) }
34
+
35
+ it "registers the child by its ID" do
36
+ expect(ultravisor[:testy]).to be_a(Ultravisor::Child)
37
+ end
38
+ end
39
+
40
+ context "with two children with the same ID" do
41
+ it "explodes" do
42
+ expect do
43
+ Ultravisor.new(
44
+ children: [
45
+ { id: :testy, klass: Object, method: :to_s },
46
+ { id: :testy, klass: Class, method: :to_s },
47
+ ]
48
+ )
49
+ end.to raise_error(Ultravisor::DuplicateChildError)
50
+ end
51
+ end
52
+
53
+ context "with a valid strategy" do
54
+ it "does not explode" do
55
+ expect { Ultravisor.new strategy: :all_for_one }.to_not raise_error
56
+ end
57
+ end
58
+
59
+ [
60
+ { strategy: :bob },
61
+ { strategy: "all_for_one" },
62
+ { strategy: ["games"] },
63
+ ].each do |s|
64
+ context "with invalid strategy #{s.inspect}" do
65
+ it "explodes" do
66
+ expect { Ultravisor.new **s }.to raise_error(ArgumentError)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,49 @@
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(:mock_child) { instance_double(Ultravisor::Child) }
10
+
11
+ describe "#remove_child" do
12
+ before(:each) do
13
+ ultravisor.instance_variable_set(:@children, [[:lamb, mock_child]])
14
+ end
15
+
16
+ context "when the ultravisor isn't running" do
17
+ it "removes the child from the list of children" do
18
+ ultravisor.remove_child(:lamb)
19
+
20
+ expect(ultravisor[:lamb]).to be(nil)
21
+ end
22
+
23
+ it "doesn't explode if asked to remove a child that doesn't exist" do
24
+ expect { ultravisor.remove_child(:no_such_child) }.to_not raise_error
25
+ end
26
+ end
27
+
28
+ context "while the ultravisor is running" do
29
+ let(:mock_thread) { instance_double(Thread) }
30
+
31
+ before(:each) do
32
+ allow(mock_child).to receive(:shutdown)
33
+ ultravisor.instance_variable_set(:@running_thread, mock_thread)
34
+ end
35
+
36
+ it "shuts down the child" do
37
+ expect(mock_child).to receive(:shutdown)
38
+
39
+ ultravisor.remove_child(:lamb)
40
+ end
41
+
42
+ it "removes the child from the list of children" do
43
+ ultravisor.remove_child(:lamb)
44
+
45
+ expect(ultravisor[:lamb]).to be(nil)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,334 @@
1
+ # frozen_string_literal: true
2
+ require_relative "../spec_helper"
3
+
4
+ require_relative "../../lib/ultravisor"
5
+
6
+ describe Ultravisor do
7
+ uses_logger
8
+
9
+ describe "#run" do
10
+ let(:mock_class) { Class.new.tap { |k| k.class_eval { def run; end } } }
11
+ let(:args) { { children: [{ id: :testy, klass: mock_class, method: :run }] } }
12
+ let(:ultravisor) { Ultravisor.new(**args) }
13
+ let(:mock_thread) { instance_double(Thread) }
14
+
15
+ before(:each) do
16
+ allow(ultravisor.instance_variable_get(:@queue)).to receive(:pop).and_return(:shutdown)
17
+ end
18
+
19
+ context "with no children" do
20
+ let(:args) { {} }
21
+
22
+ it "doesn't start anything" do
23
+ expect(Thread).to_not receive(:new)
24
+
25
+ expect { ultravisor.run }.to_not raise_error
26
+ end
27
+ end
28
+
29
+ context "when already running" do
30
+ before(:each) do
31
+ ultravisor.instance_variable_set(:@running_thread, mock_thread)
32
+ end
33
+
34
+ it "raises an exception" do
35
+ expect { ultravisor.run }.to raise_error(Ultravisor::AlreadyRunningError)
36
+ end
37
+ end
38
+
39
+ context "when the event handler gets an unknown event" do
40
+ before(:each) do
41
+ allow(ultravisor.instance_variable_get(:@queue)).to receive(:pop).and_return("wassamatta", :shutdown)
42
+ allow(logger).to receive(:error)
43
+ end
44
+
45
+ it "logs an error" do
46
+ expect(logger).to receive(:error).with(match(/^Ultravisor#.*process_events/))
47
+
48
+ ultravisor.run
49
+ end
50
+ end
51
+
52
+ context "with a single child" do
53
+ let(:child) { Ultravisor::Child.new(id: :testy, klass: Object, method: :to_s) }
54
+
55
+ before(:each) do
56
+ allow(child).to receive(:spawn)
57
+ ultravisor.instance_variable_set(:@children, [[child.id, child]])
58
+ end
59
+
60
+ it "spawns a child worker" do
61
+ expect(child).to receive(:spawn)
62
+
63
+ ultravisor.run
64
+ end
65
+
66
+ it "shuts down the child on termination" do
67
+ expect(ultravisor.instance_variable_get(:@children).first.last).to receive(:shutdown)
68
+
69
+ ultravisor.run
70
+ end
71
+
72
+ context "that terminates" do
73
+ before(:each) do
74
+ allow(ultravisor.instance_variable_get(:@queue)).to receive(:pop).and_return(child, :shutdown)
75
+ allow(ultravisor).to receive(:sleep)
76
+ end
77
+
78
+ context "within the limits of its restart policy" do
79
+ it "spawns the child again" do
80
+ expect(child).to receive(:spawn).exactly(:twice)
81
+
82
+ ultravisor.run
83
+ end
84
+
85
+ it "sleeps between restart" do
86
+ expect(ultravisor).to receive(:sleep).with(1)
87
+
88
+ ultravisor.run
89
+ end
90
+ end
91
+
92
+ context "too often for its restart policy" do
93
+ before(:each) do
94
+ allow(child).to receive(:restart?).and_raise(Ultravisor::BlownRestartPolicyError)
95
+ allow(logger).to receive(:error)
96
+ end
97
+
98
+ it "terminates the ultravisor" do
99
+ expect(ultravisor.instance_variable_get(:@queue)).to receive(:<<).with(:shutdown)
100
+
101
+ ultravisor.run
102
+ end
103
+
104
+ it "logs an error" do
105
+ expect(logger).to receive(:error)
106
+
107
+ ultravisor.run
108
+ end
109
+ end
110
+
111
+ context "with a restart_policy delay range" do
112
+ let(:child) { Ultravisor::Child.new(id: :testy, klass: mock_class, method: :run, restart_policy: { delay: 7..12 }) }
113
+
114
+ it "sleeps for a period within the range" do
115
+ expect(ultravisor).to receive(:sleep).with(be_between(7, 12))
116
+
117
+ ultravisor.run
118
+ end
119
+ end
120
+
121
+ context "while we're in the process of shutting down" do
122
+ before(:each) do
123
+ allow(ultravisor.instance_variable_get(:@queue)).to receive(:pop) do
124
+ if ultravisor.instance_variable_get(:@running_thread)
125
+ ultravisor.instance_variable_set(:@running_thread, nil)
126
+ child
127
+ else
128
+ :shutdown
129
+ end
130
+ end
131
+ end
132
+
133
+ it "doesn't respawn the child" do
134
+ expect(child).to receive(:spawn).exactly(:once)
135
+
136
+ ultravisor.run
137
+ end
138
+ end
139
+
140
+ context "with restart: :never" do
141
+ let(:child) { Ultravisor::Child.new(id: :once, klass: mock_class, restart: :never, method: :run) }
142
+
143
+ it "doesn't respawn the child" do
144
+ expect(child).to receive(:spawn).exactly(:once)
145
+
146
+ ultravisor.run
147
+ end
148
+ end
149
+
150
+ context "with restart: :on_failure" do
151
+ let(:child) { Ultravisor::Child.new(id: :once, klass: mock_class, restart: :on_failure, method: :run) }
152
+
153
+ it "doesn't respawn the child" do
154
+ expect(child).to receive(:spawn).exactly(:once)
155
+
156
+ ultravisor.run
157
+ end
158
+ end
159
+
160
+ context "with an error" do
161
+ before(:each) do
162
+ allow(logger).to receive(:error)
163
+ ex = Errno::ENOENT.new("I stiiiiiiiill haven't found, what I'm lookin' for")
164
+ ex.set_backtrace(caller)
165
+ allow(child).to receive(:termination_exception).and_return(ex)
166
+ end
167
+
168
+ it "logs the error" do
169
+ expect(logger).to receive(:error).with(match(/:testy/))
170
+
171
+ ultravisor.run
172
+ end
173
+
174
+ it "respawns the child" do
175
+ expect(child).to receive(:spawn).exactly(:twice)
176
+
177
+ ultravisor.run
178
+ end
179
+
180
+ context "with restart: :on_failure" do
181
+ let(:child) { Ultravisor::Child.new(id: :once, klass: mock_class, restart: :on_failure, method: :run) }
182
+
183
+ it "respawns the child" do
184
+ expect(child).to receive(:spawn).exactly(:twice)
185
+
186
+ ultravisor.run
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
192
+
193
+ context "with two children" do
194
+ let(:args) do
195
+ {
196
+ children: [
197
+ {
198
+ id: :one,
199
+ klass: Object,
200
+ method: :to_s,
201
+ },
202
+ {
203
+ id: :two,
204
+ klass: Object,
205
+ method: :to_s,
206
+ }
207
+ ]
208
+ }
209
+ end
210
+
211
+ it "starts the children in order of their definition" do
212
+ expect(ultravisor[:one]).to receive(:spawn).ordered
213
+ expect(ultravisor[:two]).to receive(:spawn).ordered
214
+
215
+ ultravisor.run
216
+ end
217
+
218
+ it "shuts the children down in the opposite order" do
219
+ expect(ultravisor[:two]).to receive(:shutdown).ordered
220
+ expect(ultravisor[:one]).to receive(:shutdown).ordered
221
+
222
+ ultravisor.run
223
+ end
224
+ end
225
+
226
+ context "with an all_for_one strategy" do
227
+ let(:args) do
228
+ {
229
+ strategy: :all_for_one,
230
+ children: [
231
+ {
232
+ id: :one,
233
+ klass: Object,
234
+ method: :to_s,
235
+ },
236
+ {
237
+ id: :two,
238
+ klass: Object,
239
+ method: :to_s,
240
+ },
241
+ {
242
+ id: :three,
243
+ klass: Object,
244
+ method: :to_s,
245
+ },
246
+ ]
247
+ }
248
+ end
249
+
250
+ let(:child1) { Ultravisor::Child.new(id: :one, klass: Object, method: :to_s) }
251
+ let(:child2) { Ultravisor::Child.new(id: :two, klass: Object, method: :to_s) }
252
+ let(:child3) { Ultravisor::Child.new(id: :three, klass: Object, method: :to_s) }
253
+
254
+ before(:each) do
255
+ ultravisor.instance_variable_set(:@children, [[:one, child1], [:two, child2], [:three, child3]])
256
+ allow(ultravisor.instance_variable_get(:@queue)).to receive(:pop).and_return(child2, :shutdown)
257
+ allow(ultravisor).to receive(:sleep)
258
+ ultravisor.instance_variable_set(:@running_thread, mock_thread)
259
+ end
260
+
261
+ it "shuts down all the other children in reverse order" do
262
+ expect(child3).to receive(:shutdown).ordered
263
+ expect(child1).to receive(:shutdown).ordered
264
+
265
+ ultravisor.__send__(:process_events)
266
+ end
267
+
268
+ it "starts up all children in order" do
269
+ expect(child1).to receive(:spawn).ordered
270
+ expect(child2).to receive(:spawn).ordered
271
+ expect(child3).to receive(:spawn).ordered
272
+
273
+ ultravisor.__send__(:process_events)
274
+ end
275
+ end
276
+
277
+ context "with a rest_for_one strategy" do
278
+ let(:args) do
279
+ {
280
+ strategy: :rest_for_one,
281
+ children: [
282
+ {
283
+ id: :one,
284
+ klass: Object,
285
+ method: :to_s,
286
+ },
287
+ {
288
+ id: :two,
289
+ klass: Object,
290
+ method: :to_s,
291
+ },
292
+ {
293
+ id: :three,
294
+ klass: Object,
295
+ method: :to_s,
296
+ },
297
+ {
298
+ id: :four,
299
+ klass: Object,
300
+ method: :to_s,
301
+ },
302
+ ]
303
+ }
304
+ end
305
+
306
+ let(:child1) { Ultravisor::Child.new(id: :one, klass: Object, method: :to_s) }
307
+ let(:child2) { Ultravisor::Child.new(id: :two, klass: Object, method: :to_s) }
308
+ let(:child3) { Ultravisor::Child.new(id: :three, klass: Object, method: :to_s) }
309
+ let(:child4) { Ultravisor::Child.new(id: :four, klass: Object, method: :to_s) }
310
+
311
+ before(:each) do
312
+ ultravisor.instance_variable_set(:@children, [[:one, child1], [:two, child2], [:three, child3], [:four, child4]])
313
+ allow(ultravisor.instance_variable_get(:@queue)).to receive(:pop).and_return(child2, :shutdown)
314
+ allow(ultravisor).to receive(:sleep)
315
+ ultravisor.instance_variable_set(:@running_thread, mock_thread)
316
+ end
317
+
318
+ it "shuts down only the children after the failed one, in reverse order" do
319
+ expect(child4).to receive(:shutdown).ordered
320
+ expect(child3).to receive(:shutdown).ordered
321
+
322
+ ultravisor.__send__(:process_events)
323
+ end
324
+
325
+ it "starts up all the relevant children in order" do
326
+ expect(child2).to receive(:spawn).ordered
327
+ expect(child3).to receive(:spawn).ordered
328
+ expect(child4).to receive(:spawn).ordered
329
+
330
+ ultravisor.__send__(:process_events)
331
+ end
332
+ end
333
+ end
334
+ end