sisyphus 0.2.3 → 0.2.5

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.
@@ -1,192 +1,112 @@
1
+ require 'spec_helper'
1
2
  require_relative '../../lib/sisyphus/master'
2
3
 
3
4
  module Sisyphus
4
5
  describe Master do
5
- subject(:master) { Master.new job }
6
+ subject(:master) { Master.new job, worker_pool: worker_pool }
6
7
 
7
- before(:each) { master.stub :puts }
8
+ let(:worker_pool) { double :worker_pool }
9
+
10
+ before(:each) {
11
+ allow(master).to receive(:puts)
12
+ allow(master).to receive(:sleep)
13
+ }
8
14
 
9
15
  let(:job) { double(:job) }
10
16
  let(:pipes) { [double(:reader_pipe), double(:writer_pipe)] }
11
17
 
12
- describe 'when receiving the start_worker message' do
13
- it 'forks' do
14
- master.should_receive(:fork) { 666 }
15
- master.start_worker
16
- end
17
-
18
- describe 'in the worker process' do
19
- let(:worker) { double :worker }
20
-
21
- before :each do
22
- master.stub(:fork) { nil }
23
- IO.stub(:pipe) { pipes }
24
- pipes.first.stub(:close)
25
- Process.stub(:pid) { 666 }
26
- master.stub :exit!
27
- Worker.stub(:new) { worker }
28
- worker.stub(:setup)
29
- worker.stub(:start)
30
- end
31
-
32
- it 'should setup the worker' do
33
- worker.should_receive :setup
34
- master.start_worker
35
- end
18
+ it 'creates a worker' do
19
+ execution_strategy = double :execution_strategy
20
+ master = Master.new job
21
+ allow(master).to receive(:execution_strategy) { execution_strategy }
22
+ worker = master.create_worker
23
+ expect(worker.job).to eq(job)
24
+ expect(worker.execution_strategy).to eq(execution_strategy)
25
+ end
36
26
 
37
- it 'should rename the process' do
38
- master.should_receive(:process_name=).with("Worker #{666}")
39
- master.start_worker
40
- end
27
+ describe 'when it has running workers' do
41
28
 
42
- it 'starts a worker after forking' do
43
- worker.should_receive :start
44
- master.start_worker
45
- end
29
+ let(:workers) { double :workers }
46
30
 
47
- it 'gives the writer pipe to the worker' do
48
- Worker.should_receive(:new).with(job, pipes.last, master.logger) { worker }
49
- master.start_worker
50
- end
31
+ it 'stops all workers when receiving stop_all' do
32
+ allow(worker_pool).to receive(:workers) { workers }
33
+ allow(workers).to receive(:each).and_yield({ pid: 666 }).and_yield({ pid: 667 })
34
+ allow(workers).to receive(:length).and_return(2, 1, 0)
51
35
 
52
- it 'closes the reader pipe' do
53
- pipes.first.should_receive :close
54
- master.start_worker
55
- end
36
+ expect(master).to receive(:stop_worker).with(666).ordered
37
+ expect(master).to receive(:stop_worker).with(667).ordered
56
38
 
57
- describe 'when an exception is raised' do
58
- let(:logger) { double(:logger) }
59
-
60
- it 'should log the exception' do
61
- master.stub(:logger).and_return logger
62
- pipes.last.stub(:write)
63
- worker.stub(:setup).and_raise :raised_by_spec
64
- logger.should_receive :warn
65
- master.start_worker
66
- end
67
-
68
- it 'should write to the writer pipe' do
69
- master.stub(:logger).and_return logger
70
- worker.stub(:setup).and_raise :raised_by_spec
71
- logger.stub :warn
72
- pipes.last.should_receive(:write).with Worker::UNCAUGHT_ERROR
73
- master.start_worker
74
- end
75
- end
39
+ master.stop_all
76
40
  end
77
41
 
78
- describe 'in the master process' do
42
+ describe 'and it receives stop_worker message' do
79
43
  before :each do
80
- master.stub(:fork) { 666 }
81
- IO.stub(:pipe) { pipes }
82
- pipes.last.stub(:close)
83
- end
84
-
85
- it 'increases worker_count' do
86
- master.start_worker
87
- master.worker_count.should eq(1)
88
- end
89
-
90
- it 'should open a pipe' do
91
- IO.should_receive(:pipe) { pipes }
92
- master.start_worker
44
+ allow(worker_pool).to receive(:workers) { workers }
45
+ allow(workers).to receive(:find) { |&block| block.call({ pid: 666 }) }
93
46
  end
94
47
 
95
- it 'should close the writer pipe' do
96
- pipes.last.should_receive :close
97
- master.start_worker
98
- end
99
- end
100
- end
101
-
102
- describe 'when it has running workers' do
103
- before :each do
104
- pipes.each { |p| p.stub :close }
105
- IO.stub(:pipe) { pipes }
106
- master.stub(:fork) { 666 }
107
- master.start_worker
108
- Process.stub(:kill).with('INT', 666)
109
- Process.stub(:waitpid2).with(666)
110
- end
111
-
112
- describe 'and it receives stop_worker message' do
113
48
  it 'kills a child with the INT signal' do
114
- Process.should_receive(:kill).with('INT', 666)
49
+ expect(Process).to receive(:kill).with('INT', 666)
115
50
  master.stop_worker(666)
116
51
  end
117
- end
118
-
119
- it 'stops all workers when receiving stop_all' do
120
- Process.stub(:kill).with('INT', 666)
121
- Process.stub(:wait2) { 666 }
122
52
 
123
- master.should_receive(:stop_worker).with(666).exactly(master.worker_count).times.and_call_original
124
-
125
- master.stop_all
126
- end
127
- end
128
-
129
- describe 'when there are no running workers' do
130
- describe 'and it receives stop_worker' do
131
- it 'raises an error' do
132
- expect { master.stop_worker(666) }.not_to raise_error
53
+ it 'kills nothing if no worker corresponds to the pid' do
54
+ expect(Process).not_to receive(:kill).with('INT', 667)
55
+ master.stop_worker(667)
133
56
  end
134
57
  end
135
58
 
136
- describe 'and it receives stop_all' do
137
- it 'does nothing' do
138
- master.should_not_receive(:stop_worker)
139
- master.stop_all
140
- end
141
- end
142
59
  end
143
60
 
144
61
  it 'starts the specified number of workers when started' do
145
- master = Master.new nil, workers: 3
146
- master.stub :puts
147
- master.stub :watch_for_output
148
- master.should_receive(:start_worker).exactly(3).times
62
+ master = Master.new nil, workers: 3, worker_pool: worker_pool
63
+ allow(master).to receive(:puts)
64
+ allow(master).to receive(:watch_for_output)
65
+ allow(master).to receive(:sleep)
66
+ expect(worker_pool).to receive(:spawn_worker).exactly(3).times
149
67
  master.start
150
68
  end
151
69
 
152
70
  describe 'when number of workers is zero' do
153
- let(:master) { Master.new nil, workers: 0 }
71
+ let(:master) { Master.new nil, workers: 0, worker_pool: worker_pool }
154
72
 
155
- before(:each) { master.stub :puts }
73
+ before(:each) { allow(master).to receive(:puts) }
156
74
 
157
75
  it 'should not start workers' do
158
- master.stub :watch_for_output
159
- master.should_not_receive :start_worker
76
+ allow(master).to receive(:trap_signals)
77
+ allow(master).to receive(:watch_for_output)
78
+ expect(worker_pool).not_to receive(:spawn_worker)
160
79
  master.start
161
80
  end
162
81
  end
163
82
 
164
83
  it 'attaches a signal handler when started' do
165
- Signal.should_receive(:trap).with(:INT)
166
- Signal.should_receive(:trap).with(:TTIN)
167
- Signal.should_receive(:trap).with(:TTOU)
168
- master.stub :start_worker
169
- master.stub :watch_for_output
84
+ allow(worker_pool).to receive(:spawn_worker)
85
+ allow(master).to receive(:watch_for_output)
86
+
87
+ expect(Signal).to receive(:trap).with(:TTIN)
88
+ expect(Signal).to receive(:trap).with(:INT)
89
+ expect(Signal).to receive(:trap).with(:TTOU)
90
+
170
91
  master.start
171
92
  end
172
93
 
173
94
  it 'should watch for output' do
174
- master.stub :start_worker
175
- master.should_receive :watch_for_output
95
+ allow(worker_pool).to receive(:spawn_worker)
96
+ allow(master).to receive(:trap_signals)
97
+ expect(master).to receive(:watch_for_output)
176
98
  master.start
177
99
  end
178
100
 
179
101
  it 'can resolve a wpid from a reader pipe' do
180
- IO.stub(:pipe) { pipes }
181
- pipes.each { |p| p.stub(:close) }
182
- pipes.first.stub(:fileno) { 213 }
183
- master.stub(:fork) { 666 }
184
- master.start_worker
185
-
186
- master.send(:worker_pid, pipes.first).should eq(666)
102
+ pipe = double :pipe
103
+ allow(pipe).to receive(:fileno) { 123 }
104
+ allow(worker_pool).to receive(:workers) { [{ pid: 666, reader: pipe }] }
105
+ expect(master.send(:worker_pid, pipe)).to eq(666)
187
106
  end
188
107
 
189
108
  it 'raises if it can\'t resolve a wpid from a reader pipe' do
109
+ allow(worker_pool).to receive(:workers) { [] }
190
110
  expect { master.send(:worker_pid, pipes.first) }.to raise_error("Unknown worker pipe")
191
111
  end
192
112
  end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+ require_relative '../../lib/sisyphus/simple_execution_strategy'
3
+
4
+ module Sisyphus
5
+ describe SimpleExecutionStrategy do
6
+
7
+ let(:job) { double :job }
8
+ let(:strategy) { SimpleExecutionStrategy.new }
9
+
10
+ it 'should perform the job when executed' do
11
+ expect(job).to receive(:perform)
12
+ strategy.execute job
13
+ end
14
+
15
+ it 'should call the error_handler if the job fails' do
16
+ error_message = "This is a horrible failure.. The Universe is probably ending!"
17
+ error = Exception.new(error_message)
18
+ process_name = "uber awesome process name"
19
+ allow(job).to receive(:perform) { fail error }
20
+ allow(strategy).to receive(:process_name) { process_name }
21
+ strategy.execute job, ->(name, raised_error) {
22
+ expect(name).to eq(process_name)
23
+ expect(raised_error).to eq(error)
24
+ }
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+ require_relative '../../lib/sisyphus/master'
3
+ require_relative '../../lib/sisyphus/worker_pool'
4
+
5
+ module Sisyphus
6
+ describe WorkerPool do
7
+
8
+ subject(:worker_pool) { WorkerPool.new worker_factory }
9
+
10
+ describe 'when receiving the spawn_worker message' do
11
+
12
+ let(:job) { double :job }
13
+ let(:worker_factory) { Master.new(job) }
14
+ let(:pipes) { [double(:output), double(:input)] }
15
+
16
+ it 'retrieves a worker from the worker_factory' do
17
+ allow(worker_pool).to receive(:fork) { 3267 }
18
+ expect(worker_factory).to receive(:create_worker).and_call_original
19
+ worker_pool.spawn_worker
20
+ end
21
+
22
+ it 'forks' do
23
+ expect(worker_pool).to receive(:fork) { 666 }
24
+ worker_pool.spawn_worker
25
+ end
26
+
27
+ describe 'in the worker process' do
28
+
29
+ let(:worker) { double :worker }
30
+
31
+ before :each do
32
+ allow(worker_pool).to receive(:fork) { nil }
33
+ allow(worker_pool).to receive(:process_name=)
34
+ allow(worker).to receive(:start)
35
+ allow(worker).to receive(:atfork_child)
36
+ end
37
+
38
+ it 'runs Worker#atfork_child' do
39
+ allow(worker_factory).to receive(:create_worker) { worker }
40
+ expect(worker).to receive(:atfork_child).with(no_args)
41
+ worker_pool.spawn_worker
42
+ end
43
+
44
+ it 'renames the process' do
45
+ allow(worker_factory).to receive(:create_worker) { worker }
46
+ expect(worker_pool).to receive(:process_name=).with("Worker #{Process.pid}")
47
+ worker_pool.spawn_worker
48
+ end
49
+
50
+ it 'starts the worker' do
51
+ allow(worker_factory).to receive(:create_worker) { worker }
52
+ allow(worker).to receive(:atfork_child)
53
+ expect(worker).to receive(:start)
54
+ worker_pool.spawn_worker
55
+ end
56
+
57
+ end
58
+
59
+ describe 'in the master process' do
60
+
61
+ before :each do
62
+ allow(worker_pool).to receive(:fork) { Process.pid }
63
+ allow(pipes.last).to receive(:close)
64
+ allow(IO).to receive(:pipe) { pipes }
65
+ end
66
+
67
+ it 'closes the input pipe' do
68
+ worker = double :worker
69
+ allow(worker).to receive(:to_master)
70
+ allow(worker_factory).to receive(:create_worker) { worker }
71
+ expect(worker).to receive(:atfork_parent).with(no_args)
72
+ worker_pool.spawn_worker
73
+ end
74
+
75
+ it 'adds the worker pid and output pipe to the list of workers' do
76
+ expect(worker_pool.workers).to receive(:<<).with({ pid: Process.pid, reader: pipes.first })
77
+ worker_pool.spawn_worker
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -1,126 +1,128 @@
1
+ require 'spec_helper'
1
2
  require_relative '../../lib/sisyphus/worker'
2
3
 
3
4
  module Sisyphus
4
5
  describe Worker do
5
6
  let(:job) { double :job }
6
- let(:output) { double :pipe }
7
+ let(:execution_strategy) { double :execution_strategy }
7
8
  let(:logger) { double :logger }
8
- let(:worker) { Worker.new job, output, logger }
9
9
 
10
- it 'traps signals when started' do
11
- worker.stub :exit!
12
- worker.instance_variable_set(:@stopped, true)
13
- worker.should_receive :trap_signals
10
+ subject(:worker) { Worker.new job, execution_strategy, logger }
11
+
12
+ it 'sets up itself and executes run when started' do
13
+ expect(worker).to receive(:setup).ordered
14
+ expect(worker).to receive(:run).ordered
14
15
  worker.start
15
16
  end
16
17
 
17
- it 'exits when it has been stopped' do
18
- worker.instance_variable_set(:@stopped, true)
19
- worker.should_receive :exit!
20
- worker.start
18
+ it 'traps INT signals before setting up the job' do
19
+ allow(job).to receive(:respond_to?).with(:setup) { true }
20
+ expect(Signal).to receive(:trap).with('INT').ordered
21
+ expect(job).to receive(:setup).ordered
22
+ worker.setup
21
23
  end
22
24
 
23
- context 'when job does not respond to :setup' do
24
- it 'does not call job.setup' do
25
- job.stub(:respond_to?).with(:setup) { false }
26
- job.should_not_receive :setup
27
- Worker.new job, output, logger
28
- end
25
+ it 'does not perform the job when it has been stopped' do
26
+ worker.stop
27
+ allow(worker).to receive(:exit!)
28
+ expect(worker).not_to receive(:perform_job)
29
+ worker.run
29
30
  end
30
31
 
31
- context 'when job responds to :setup' do
32
- it 'sets up the job' do
33
- job.stub(:respond_to?).with(:setup) { true }
34
- job.should_receive :setup
35
- worker.setup
36
- end
32
+ it 'will not run if it has not been set up' do
33
+ expect(worker).not_to receive(:perform_job)
34
+ allow(worker).to receive(:exit!)
35
+ worker.run
37
36
  end
38
37
 
39
- context 'in the child process' do
40
- before :each do
41
- worker.stub(:fork) { nil }
42
- end
38
+ it 'uses execution_strategy to perform the job' do
39
+ expect(execution_strategy).to receive(:execute).with job, an_instance_of(Proc)
40
+ worker.perform_job
41
+ end
42
+
43
+ it 'passes the error_handler to the execution strategy' do
44
+ allow(execution_strategy).to receive(:execute).with job, an_instance_of(Proc)
45
+ expect(worker).to receive(:error_handler) { ->{} }
46
+ worker.perform_job
47
+ end
48
+
49
+ it 'only closes the output in atfork_parent' do
50
+ expect(worker.output).to receive(:close)
51
+ expect(worker.to_master).not_to receive(:close)
52
+ worker.atfork_parent
53
+ end
54
+
55
+ it 'only closes the to_master in atfork_child' do
56
+ expect(worker.to_master).to receive(:close)
57
+ expect(worker.output).not_to receive(:close)
58
+ worker.atfork_child
59
+ end
43
60
 
44
- it 'should perform the job' do
45
- job.should_receive :perform
46
- worker.stub :exit!
47
- worker.send :perform_job
61
+ context 'the error_handler' do
62
+
63
+ it 'writes the UNCAUGHT_ERROR to output' do
64
+ allow(logger).to receive(:warn)
65
+ expect(worker.output).to receive(:write).with Worker::UNCAUGHT_ERROR
66
+ worker.error_handler.call(:name, :error)
48
67
  end
49
68
 
50
- it 'should exit after having performed the job' do
51
- job.stub :perform
52
- worker.should_receive(:exit!).with 0
53
- worker.send :perform_job
69
+ it 'logs the error being thrown' do
70
+ the_name = :the_name
71
+ the_exception = :the_exception
72
+ expect(worker.logger).to receive(:warn) do |name, &block|
73
+ expect(name).to eq(the_name)
74
+ expect(block.call).to eq(the_exception)
75
+ end
76
+ worker.error_handler.call(the_name, the_exception)
54
77
  end
55
78
 
56
- it 'should change process name' do
57
- job.stub :perform
58
- worker.stub :exit!
59
- Process.stub(:ppid) { 666 }
60
- worker.should_receive(:process_name=).with "Child of worker 666"
61
- worker.send :perform_job
79
+ it 'does not write UNCAUGHT_ERROR to output if the worker is stopped' do
80
+ allow(worker).to receive(:stopped?) { true }
81
+ expect(worker.output).to_not receive(:write)
82
+ worker.error_handler.call(:name, :error)
83
+ expect(worker.output).to_not receive(:write)
84
+ worker.error_handler.call(:name, :error)
62
85
  end
63
86
 
64
- context 'when job.perform raises an error' do
65
- it 'should exit with a non-zero status' do
66
- logger.stub :warn
67
- job.stub(:perform).and_raise Exception.new "should be rescued!"
68
- worker.should_receive(:exit!).with(1)
69
- worker.send :perform_job
70
- end
87
+ end
71
88
 
72
- it 'should log the raised error' do
73
- worker.stub(:exit!).with(1)
74
- job.stub(:perform).and_raise Exception.new "should be rescued!"
75
- logger.should_receive :warn
76
- worker.send :perform_job
77
- end
89
+ context 'when job does not respond to :setup' do
90
+ it 'does not call job.setup' do
91
+ allow(job).to receive(:respond_to?).with(:setup) { false }
92
+ expect(job).not_to receive(:setup)
93
+ worker.setup
78
94
  end
79
95
  end
80
96
 
81
- context 'in the worker process' do
82
- let(:status) { double(:status) }
97
+ context 'when job responds to :setup' do
83
98
 
84
99
  before :each do
85
- worker.stub(:fork) { 666 }
100
+ allow(job).to receive(:respond_to?).with(:setup) { true }
86
101
  end
87
102
 
88
- it 'spawns a process and waits for it to finish' do
89
- worker.should_receive(:fork) { 666 }
90
- status.stub(:success?) { true }
91
- Process.should_receive(:waitpid2).with(666) { [666, status] }
92
- worker.send :perform_job
103
+ it 'sets up the job' do
104
+ expect(job).to receive(:setup)
105
+ worker.setup
93
106
  end
94
107
 
95
- context 'when exit status from the child is non-zero' do
108
+ context 'and job#setup raises an exception' do
109
+
96
110
  before :each do
97
- status.stub(:success?) { false }
98
- Process.stub(:waitpid2) { [666, status] }
111
+ allow(job).to receive(:setup).and_raise(Exception)
112
+ allow(logger).to receive(:warn)
99
113
  end
100
114
 
101
- it 'writes an error message to the output' do
102
- output.should_receive(:write).with Worker::UNCAUGHT_ERROR
103
- worker.send :perform_job
115
+ it 'handles the exception' do
116
+ expect { worker.setup }.not_to raise_error(Exception)
104
117
  end
105
118
 
106
- it "doesn't write error byte to output when it has been stopped" do
107
- worker.stub(:stopped?) { true }
108
- output.should_not_receive :write
109
- worker.send :perform_job
119
+ it 'calls the error_handler in the rescue block' do
120
+ expect(worker.output).to receive(:write).with(Worker::UNCAUGHT_ERROR)
121
+ worker.setup
110
122
  end
111
- end
112
123
 
113
- context 'when exit status from child is zero' do
114
- before :each do
115
- status.stub(:success?) { true }
116
- Process.stub(:waitpid2) { [666, status] }
117
- end
118
-
119
- it "doesn't write error byte to output" do
120
- output.should_not_receive :write
121
- worker.send :perform_job
122
- end
123
124
  end
124
125
  end
126
+
125
127
  end
126
128
  end
@@ -0,0 +1,17 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ RSpec.configure do |config|
8
+ config.treat_symbols_as_metadata_keys_with_true_values = true
9
+ config.run_all_when_everything_filtered = true
10
+ config.filter_run :focus
11
+
12
+ # Run specs in random order to surface order dependencies. If you find an
13
+ # order dependency and want to debug it, you can fix the order by providing
14
+ # the seed, which is printed after each run.
15
+ # --seed 1234
16
+ config.order = 'random'
17
+ end