service_skeleton 1.0.3 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
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