sanford 0.17.0 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,84 @@
1
+ require 'assert'
2
+ require 'sanford/io_pipe'
3
+
4
+ require 'thread'
5
+
6
+ class Sanford::IOPipe
7
+
8
+ class UnitTests < Assert::Context
9
+ desc "Sanford::IOPipe"
10
+ setup do
11
+ # mimic how IO.select responds
12
+ @io_select_response = Factory.boolean ? [[NULL], [], []] : nil
13
+ @io_select_called_with = nil
14
+ Assert.stub(IO, :select) do |*args|
15
+ @io_select_called_with = args
16
+ @io_select_response
17
+ end
18
+
19
+ @io_pipe = Sanford::IOPipe.new
20
+ end
21
+ subject{ @io_pipe }
22
+
23
+ should have_readers :reader, :writer
24
+ should have_imeths :setup, :teardown
25
+ should have_imeths :read, :write, :wait
26
+
27
+ should "default its reader and writer" do
28
+ assert_same NULL, subject.reader
29
+ assert_same NULL, subject.writer
30
+ end
31
+
32
+ should "change its reader/writer to an IO pipe when setup" do
33
+ subject.setup
34
+ assert_not_same NULL, subject.reader
35
+ assert_not_same NULL, subject.writer
36
+ assert_instance_of IO, subject.reader
37
+ assert_instance_of IO, subject.writer
38
+ end
39
+
40
+ should "close its reader/writer and set them to defaults when torn down" do
41
+ subject.setup
42
+ reader = subject.reader
43
+ writer = subject.writer
44
+
45
+ subject.teardown
46
+ assert_true reader.closed?
47
+ assert_true writer.closed?
48
+ assert_same NULL, subject.reader
49
+ assert_same NULL, subject.writer
50
+ end
51
+
52
+ should "be able to read/write values" do
53
+ subject.setup
54
+
55
+ value = Factory.string(NUMBER_OF_BYTES)
56
+ subject.write(value)
57
+ assert_equal value, subject.read
58
+ end
59
+
60
+ should "only read/write a fixed number of bytes" do
61
+ subject.setup
62
+
63
+ value = Factory.string
64
+ subject.write(value)
65
+ assert_equal value[0, NUMBER_OF_BYTES], subject.read
66
+ end
67
+
68
+ should "be able to wait until there is something to read" do
69
+ subject.setup
70
+
71
+ result = subject.wait
72
+ exp = [[subject.reader], nil, nil, nil]
73
+ assert_equal exp, @io_select_called_with
74
+ assert_equal !!@io_select_response, result
75
+
76
+ timeout = Factory.integer
77
+ subject.wait(timeout)
78
+ exp = [[subject.reader], nil, nil, timeout]
79
+ assert_equal exp, @io_select_called_with
80
+ end
81
+
82
+ end
83
+
84
+ end
@@ -1,6 +1,7 @@
1
1
  require 'assert'
2
2
  require 'sanford/process'
3
3
 
4
+ require 'sanford/io_pipe'
4
5
  require 'sanford/server'
5
6
  require 'test/support/pid_file_spy'
6
7
 
@@ -9,22 +10,37 @@ class Sanford::Process
9
10
  class UnitTests < Assert::Context
10
11
  desc "Sanford::Process"
11
12
  setup do
13
+ @current_env_server_fd = ENV['SANFORD_SERVER_FD']
14
+ @current_env_client_fds = ENV['SANFORD_CLIENT_FDS']
15
+ @current_env_skip_daemonize = ENV['SANFORD_SKIP_DAEMONIZE']
16
+ ENV.delete('SANFORD_SERVER_FD')
17
+ ENV.delete('SANFORD_CLIENT_FDS')
18
+ ENV.delete('SANFORD_SKIP_DAEMONIZE')
19
+
12
20
  @process_class = Sanford::Process
13
21
  end
22
+ teardown do
23
+ ENV['SANFORD_SKIP_DAEMONIZE'] = @current_env_skip_daemonize
24
+ ENV['SANFORD_CLIENT_FDS'] = @current_env_client_fds
25
+ ENV['SANFORD_SERVER_FD'] = @current_env_server_fd
26
+ end
14
27
  subject{ @process_class }
15
28
 
29
+ should "know its wait for signals timeout" do
30
+ assert_equal 15, WAIT_FOR_SIGNALS_TIMEOUT
31
+ end
32
+
33
+ should "know its signal strings" do
34
+ assert_equal 'H', HALT
35
+ assert_equal 'S', STOP
36
+ assert_equal 'R', RESTART
37
+ end
38
+
16
39
  end
17
40
 
18
41
  class InitTests < UnitTests
19
42
  desc "when init"
20
43
  setup do
21
- @current_env_server_fd = ENV['SANFORD_SERVER_FD']
22
- @current_env_client_fds = ENV['SANFORD_CLIENT_FDS']
23
- @current_env_skip_daemonize = ENV['SANFORD_SKIP_DAEMONIZE']
24
- ENV.delete('SANFORD_SERVER_FD')
25
- ENV.delete('SANFORD_CLIENT_FDS')
26
- ENV.delete('SANFORD_SKIP_DAEMONIZE')
27
-
28
44
  @server_spy = ServerSpy.new
29
45
 
30
46
  @pid_file_spy = PIDFileSpy.new(Factory.integer)
@@ -37,14 +53,9 @@ class Sanford::Process
37
53
 
38
54
  @process = @process_class.new(@server_spy)
39
55
  end
40
- teardown do
41
- ENV['SANFORD_SKIP_DAEMONIZE'] = @current_env_skip_daemonize
42
- ENV['SANFORD_CLIENT_FDS'] = @current_env_client_fds
43
- ENV['SANFORD_SERVER_FD'] = @current_env_server_fd
44
- end
45
56
  subject{ @process }
46
57
 
47
- should have_readers :server, :name, :pid_file, :restart_cmd
58
+ should have_readers :server, :name, :pid_file, :signal_io, :restart_cmd
48
59
  should have_readers :server_ip, :server_port, :server_fd, :client_fds
49
60
  should have_imeths :run, :daemonize?
50
61
 
@@ -52,16 +63,17 @@ class Sanford::Process
52
63
  assert_equal @server_spy, subject.server
53
64
  end
54
65
 
55
- should "know its name, pid file and restart cmd" do
56
- expected = "sanford-#{@server_spy.name}-" \
57
- "#{@server_spy.configured_ip}-#{@server_spy.configured_port}"
58
- assert_equal expected, subject.name
59
- assert_equal @pid_file_spy, subject.pid_file
60
- assert_equal @restart_cmd_spy, subject.restart_cmd
66
+ should "know its name, pid file, signal io and restart cmd" do
67
+ exp = "sanford: #{@server_spy.process_label}"
68
+ assert_equal exp, subject.name
69
+
70
+ assert_equal @pid_file_spy, subject.pid_file
71
+ assert_instance_of Sanford::IOPipe, subject.signal_io
72
+ assert_equal @restart_cmd_spy, subject.restart_cmd
61
73
  end
62
74
 
63
75
  should "know its server ip, port and file descriptor" do
64
- assert_equal @server_spy.configured_ip, subject.server_ip
76
+ assert_equal @server_spy.configured_ip, subject.server_ip
65
77
  assert_equal @server_spy.configured_port, subject.server_port
66
78
  assert_nil subject.server_fd
67
79
  end
@@ -124,28 +136,14 @@ class Sanford::Process
124
136
 
125
137
  @current_process_name = $0
126
138
 
127
- @term_signal_trap_block = nil
128
- @term_signal_trap_called = false
129
- Assert.stub(::Signal, :trap).with("TERM") do |&block|
130
- @term_signal_trap_block = block
131
- @term_signal_trap_called = true
132
- end
133
-
134
- @int_signal_trap_block = nil
135
- @int_signal_trap_called = false
136
- Assert.stub(::Signal, :trap).with("INT") do |&block|
137
- @int_signal_trap_block = block
138
- @int_signal_trap_called = true
139
- end
140
-
141
- @usr2_signal_trap_block = nil
142
- @usr2_signal_trap_called = false
143
- Assert.stub(::Signal, :trap).with("USR2") do |&block|
144
- @usr2_signal_trap_block = block
145
- @usr2_signal_trap_called = true
139
+ @signal_traps = []
140
+ Assert.stub(::Signal, :trap) do |signal, &block|
141
+ @signal_traps << SignalTrap.new(signal, block)
146
142
  end
147
143
  end
148
144
  teardown do
145
+ @process.signal_io.write(HALT)
146
+ @thread.join if @thread
149
147
  $0 = @current_process_name
150
148
  end
151
149
 
@@ -154,58 +152,74 @@ class Sanford::Process
154
152
  class RunTests < RunSetupTests
155
153
  desc "and run"
156
154
  setup do
157
- @process.run
155
+ @wait_timeout = nil
156
+ Assert.stub(@process.signal_io, :wait) do |timeout|
157
+ @wait_timeout = timeout
158
+ sleep 2*JOIN_SECONDS
159
+ false
160
+ end
161
+
162
+ @thread = Thread.new{ @process.run }
163
+ @thread.join(JOIN_SECONDS)
164
+ end
165
+ teardown do
166
+ # manually unstub or the process thread will hang forever
167
+ Assert.unstub(@process.signal_io, :wait)
158
168
  end
159
169
 
160
- should "not have daemonized the process" do
170
+ should "not daemonize the process" do
161
171
  assert_false @daemonize_called
162
172
  end
163
173
 
164
- should "have started the server listening" do
174
+ should "start the server listening" do
165
175
  assert_true @server_spy.listen_called
166
- expected = [ subject.server_ip, subject.server_port ]
167
- assert_equal expected, @server_spy.listen_args
176
+ exp = [subject.server_ip, subject.server_port]
177
+ assert_equal exp, @server_spy.listen_args
168
178
  end
169
179
 
170
- should "have set the process name" do
180
+ should "set the process name" do
171
181
  assert_equal $0, subject.name
172
182
  end
173
183
 
174
- should "have written the PID file" do
184
+ should "write its PID file" do
175
185
  assert_true @pid_file_spy.write_called
176
186
  end
177
187
 
178
- should "have trapped signals" do
179
- assert_true @term_signal_trap_called
180
- assert_false @server_spy.stop_called
181
- @term_signal_trap_block.call
182
- assert_true @server_spy.stop_called
183
-
184
- assert_true @int_signal_trap_called
185
- assert_false @server_spy.halt_called
186
- @int_signal_trap_block.call
187
- assert_true @server_spy.halt_called
188
-
189
- assert_true @usr2_signal_trap_called
190
- assert_false @server_spy.pause_called
191
- @usr2_signal_trap_block.call
192
- assert_true @server_spy.pause_called
188
+ should "trap signals" do
189
+ assert_equal 3, @signal_traps.size
190
+ assert_equal ['INT', 'TERM', 'USR2'], @signal_traps.map(&:signal)
193
191
  end
194
192
 
195
- should "have started the server" do
193
+ should "start the server" do
196
194
  assert_true @server_spy.start_called
197
195
  end
198
196
 
199
- should "have joined the server thread" do
200
- assert_true @server_spy.thread.join_called
197
+ should "sleep its thread waiting for signals" do
198
+ assert_equal WAIT_FOR_SIGNALS_TIMEOUT, @wait_timeout
199
+ assert_equal 'sleep', @thread.status
201
200
  end
202
201
 
203
202
  should "not run the restart cmd" do
204
- assert_false @restart_cmd_spy.run_called
203
+ assert_nil @restart_cmd_spy.run_called_for
205
204
  end
206
205
 
207
- should "have removed the PID file" do
208
- assert_true @pid_file_spy.remove_called
206
+ end
207
+
208
+ class SignalTrapsTests < RunSetupTests
209
+ desc "signal traps"
210
+ setup do
211
+ # setup the io pipe so we can see whats written to it
212
+ @process.signal_io.setup
213
+ end
214
+ teardown do
215
+ @process.signal_io.teardown
216
+ end
217
+
218
+ should "write the signals to processes signal IO" do
219
+ @signal_traps.each do |signal_trap|
220
+ signal_trap.block.call
221
+ assert_equal signal_trap.signal, subject.signal_io.read
222
+ end
209
223
  end
210
224
 
211
225
  end
@@ -214,7 +228,8 @@ class Sanford::Process
214
228
  desc "that should daemonize is run"
215
229
  setup do
216
230
  Assert.stub(@process, :daemonize?){ true }
217
- @process.run
231
+ @thread = Thread.new{ @process.run }
232
+ @thread.join(JOIN_SECONDS)
218
233
  end
219
234
 
220
235
  should "have daemonized the process" do
@@ -228,13 +243,14 @@ class Sanford::Process
228
243
  setup do
229
244
  ENV['SANFORD_SERVER_FD'] = Factory.integer.to_s
230
245
  @process = @process_class.new(@server_spy)
231
- @process.run
246
+ @thread = Thread.new{ @process.run }
247
+ @thread.join(JOIN_SECONDS)
232
248
  end
233
249
 
234
250
  should "have used the file descriptor when listening" do
235
251
  assert_true @server_spy.listen_called
236
- expected = [ @process.server_fd ]
237
- assert_equal expected, @server_spy.listen_args
252
+ exp = [@process.server_fd]
253
+ assert_equal exp, @server_spy.listen_args
238
254
  end
239
255
 
240
256
  end
@@ -245,7 +261,8 @@ class Sanford::Process
245
261
  @client_fds = [ Factory.integer, Factory.integer ]
246
262
  ENV['SANFORD_CLIENT_FDS'] = @client_fds.join(',')
247
263
  @process = @process_class.new(@server_spy)
248
- @process.run
264
+ @thread = Thread.new{ @process.run }
265
+ @thread.join(JOIN_SECONDS)
249
266
  end
250
267
 
251
268
  should "have used the client file descriptors when starting" do
@@ -255,25 +272,120 @@ class Sanford::Process
255
272
 
256
273
  end
257
274
 
258
- class RunAndServerPausedTests < RunSetupTests
259
- desc "then run and then paused"
275
+ class RunAndHaltTests < RunSetupTests
276
+ desc "and run with a halt signal"
260
277
  setup do
261
- server_fd = Factory.integer
262
- Assert.stub(@server_spy, :file_descriptor){ server_fd }
263
- client_fds = [ Factory.integer, Factory.integer ]
264
- Assert.stub(@server_spy, :client_file_descriptors){ client_fds }
278
+ @thread = Thread.new{ @process.run }
279
+ @thread.join(JOIN_SECONDS)
280
+ @process.signal_io.write(HALT)
281
+ @thread.join(JOIN_SECONDS)
282
+ end
283
+
284
+ should "halt its server" do
285
+ assert_true @server_spy.halt_called
286
+ assert_equal [true], @server_spy.halt_args
287
+ end
288
+
289
+ should "not set the env var to skip daemonize" do
290
+ assert_equal @current_env_skip_daemonize, ENV['SANFORD_SKIP_DAEMONIZE']
291
+ end
292
+
293
+ should "not run the restart cmd" do
294
+ assert_nil @restart_cmd_spy.run_called_for
295
+ end
296
+
297
+ should "remove the PID file" do
298
+ assert_true @pid_file_spy.remove_called
299
+ end
300
+
301
+ end
302
+
303
+ class RunAndStopTests < RunSetupTests
304
+ desc "and run with a stop signal"
305
+ setup do
306
+ @thread = Thread.new{ @process.run }
307
+ @thread.join(JOIN_SECONDS)
308
+ @process.signal_io.write(STOP)
309
+ @thread.join(JOIN_SECONDS)
310
+ end
311
+
312
+ should "stop its server" do
313
+ assert_true @server_spy.stop_called
314
+ assert_equal [true], @server_spy.stop_args
315
+ end
316
+
317
+ should "not set the env var to skip daemonize" do
318
+ assert_equal @current_env_skip_daemonize, ENV['SANFORD_SKIP_DAEMONIZE']
319
+ end
320
+
321
+ should "not run the restart cmd" do
322
+ assert_nil @restart_cmd_spy.run_called_for
323
+ end
324
+
325
+ should "remove the PID file" do
326
+ assert_true @pid_file_spy.remove_called
327
+ end
328
+
329
+ end
265
330
 
266
- # mimicing pause being called by a signal, after the thread is joined
267
- @server_spy.thread.on_join{ @server_spy.pause }
268
- @process.run
331
+ class RunAndRestartTests < RunSetupTests
332
+ desc "and run with a restart signal"
333
+ setup do
334
+ @thread = Thread.new{ @process.run }
335
+ @thread.join(JOIN_SECONDS)
336
+ @process.signal_io.write(RESTART)
337
+ @thread.join(JOIN_SECONDS)
269
338
  end
270
339
 
271
- should "set env vars for restarting and run the restart cmd" do
272
- assert_equal @server_spy.file_descriptor.to_s, ENV['SANFORD_SERVER_FD']
273
- expected = @server_spy.client_file_descriptors.join(',')
274
- assert_equal expected, ENV['SANFORD_CLIENT_FDS']
275
- assert_equal 'yes', ENV['SANFORD_SKIP_DAEMONIZE']
276
- assert_true @restart_cmd_spy.run_called
340
+ should "pause its server" do
341
+ assert_true @server_spy.pause_called
342
+ assert_equal [true], @server_spy.pause_args
343
+ end
344
+
345
+ should "run the restart cmd" do
346
+ assert_equal @server_spy, @restart_cmd_spy.run_called_for
347
+ end
348
+
349
+ end
350
+
351
+ class RunWithServerCrashTests < RunSetupTests
352
+ desc "and run with the server crashing"
353
+ setup do
354
+ Assert.stub(@process.signal_io, :wait) do |timeout|
355
+ sleep JOIN_SECONDS * 0.5 # ensure this has time to run before the thread
356
+ # joins below
357
+ false
358
+ end
359
+
360
+ @thread = Thread.new{ @process.run }
361
+ @thread.join(JOIN_SECONDS)
362
+ @server_spy.start_called = false
363
+ @thread.join(JOIN_SECONDS)
364
+ end
365
+ teardown do
366
+ # manually unstub or the process thread will hang forever
367
+ Assert.unstub(@process.signal_io, :wait)
368
+ end
369
+
370
+ should "re-start its server" do
371
+ assert_true @server_spy.start_called
372
+ end
373
+
374
+ end
375
+
376
+ class RunWithInvalidSignalTests < RunSetupTests
377
+ desc "and run with unsupported signals"
378
+ setup do
379
+ # ruby throws an argument error if the OS doesn't support a signal
380
+ Assert.stub(::Signal, :trap){ raise ArgumentError }
381
+
382
+ @thread = Thread.new{ @process.run }
383
+ @thread.join(JOIN_SECONDS)
384
+ end
385
+
386
+ should "start normally" do
387
+ assert_true @server_spy.start_called
388
+ assert_equal 'sleep', @thread.status
277
389
  end
278
390
 
279
391
  end
@@ -289,6 +401,12 @@ class Sanford::Process
289
401
  Assert.stub(File, :stat).with(Dir.pwd){ @ruby_pwd_stat }
290
402
  Assert.stub(File, :stat).with(ENV['PWD']){ env_pwd_stat }
291
403
 
404
+ @server_spy = ServerSpy.new
405
+ server_fd = Factory.integer
406
+ Assert.stub(@server_spy, :file_descriptor){ server_fd }
407
+ client_fds = Factory.integer(3).times.map{ Factory.integer }
408
+ Assert.stub(@server_spy, :client_file_descriptors){ client_fds }
409
+
292
410
  @chdir_called_with = nil
293
411
  Assert.stub(Dir, :chdir){ |*args| @chdir_called_with = args }
294
412
 
@@ -317,10 +435,38 @@ class Sanford::Process
317
435
  assert_equal [Gem.ruby, $0, ARGV].flatten, subject.argv
318
436
  end
319
437
 
320
- should "change the dir and run a kernel exec when run" do
321
- subject.run
322
- assert_equal [subject.dir], @chdir_called_with
323
- assert_equal subject.argv, @exec_called_with
438
+ if RUBY_VERSION == '1.8.7'
439
+
440
+ should "set env vars, change the dir and kernel exec when run" do
441
+ subject.run(@server_spy)
442
+
443
+ assert_equal @server_spy.file_descriptor.to_s, ENV['SANFORD_SERVER_FD']
444
+ exp = @server_spy.client_file_descriptors.join(',')
445
+ assert_equal exp, ENV['SANFORD_CLIENT_FDS']
446
+ assert_equal 'yes', ENV['SANFORD_SKIP_DAEMONIZE']
447
+
448
+ assert_equal [subject.dir], @chdir_called_with
449
+ assert_equal subject.argv, @exec_called_with
450
+ end
451
+
452
+ else
453
+
454
+ should "kernel exec when run" do
455
+ subject.run(@server_spy)
456
+
457
+ env = {
458
+ 'SANFORD_SERVER_FD' => @server_spy.file_descriptor.to_s,
459
+ 'SANFORD_CLIENT_FDS' => @server_spy.client_file_descriptors.join(','),
460
+ 'SANFORD_SKIP_DAEMONIZE' => 'yes'
461
+ }
462
+ fd_redirects = (
463
+ [@server_spy.file_descriptor] +
464
+ @server_spy.client_file_descriptors
465
+ ).inject({}){ |h, fd| h.merge!(fd => fd) }
466
+ options = { :chdir => subject.dir }.merge!(fd_redirects)
467
+ assert_equal ([env] + subject.argv + [options]), @exec_called_with
468
+ end
469
+
324
470
  end
325
471
 
326
472
  end
@@ -364,31 +510,35 @@ class Sanford::Process
364
510
 
365
511
  end
366
512
 
513
+ SignalTrap = Struct.new(:signal, :block)
514
+
367
515
  class ServerSpy
368
516
  include Sanford::Server
369
517
 
370
- name Factory.string
371
- ip Factory.string
372
- port Factory.integer
518
+ name Factory.string
519
+ ip Factory.string
520
+ port Factory.integer
373
521
  pid_file Factory.file_path
374
522
 
375
- attr_reader :listen_called, :start_called
376
- attr_reader :stop_called, :halt_called, :pause_called
377
- attr_reader :listen_args, :start_args
378
- attr_reader :thread
523
+ attr_accessor :process_label, :listen_called
524
+ attr_accessor :start_called, :stop_called, :halt_called, :pause_called
525
+ attr_reader :listen_args, :start_args, :stop_args, :halt_args, :pause_args
379
526
 
380
527
  def initialize(*args)
381
528
  super
382
- @listen_called = false
383
- @start_called = false
384
- @stop_called = false
385
- @halt_called = false
386
- @pause_called = false
387
529
 
388
- @listen_args = nil
389
- @start_args = nil
530
+ @process_label = Factory.string
390
531
 
391
- @thread = ThreadSpy.new
532
+ @listen_args = nil
533
+ @listen_called = false
534
+ @start_args = nil
535
+ @start_called = false
536
+ @stop_args = nil
537
+ @stop_called = false
538
+ @halt_args = nil
539
+ @halt_called = false
540
+ @pause_args = nil
541
+ @pause_called = false
392
542
  end
393
543
 
394
544
  def listen(*args)
@@ -399,53 +549,38 @@ class Sanford::Process
399
549
  def start(*args)
400
550
  @start_args = args
401
551
  @start_called = true
402
- @thread
403
552
  end
404
553
 
405
554
  def stop(*args)
555
+ @stop_args = args
406
556
  @stop_called = true
407
557
  end
408
558
 
409
559
  def halt(*args)
560
+ @halt_args = args
410
561
  @halt_called = true
411
562
  end
412
563
 
564
+
413
565
  def pause(*args)
566
+ @pause_args = args
414
567
  @pause_called = true
415
568
  end
416
569
 
417
- def paused?
418
- @pause_called
419
- end
420
- end
421
-
422
- class ThreadSpy
423
- attr_reader :join_called, :on_join_proc
424
-
425
- def initialize
426
- @join_called = false
427
- @on_join_proc = proc{ }
428
- end
429
-
430
- def on_join(&block)
431
- @on_join_proc = block
432
- end
433
-
434
- def join
435
- @join_called = true
436
- @on_join_proc.call
570
+ def running?
571
+ !!@start_called
437
572
  end
438
573
  end
439
574
 
440
575
  class RestartCmdSpy
441
- attr_reader :run_called
576
+ attr_reader :run_called_for
442
577
 
443
578
  def initialize
444
- @run_called = false
579
+ @run_called_for = nil
445
580
  end
446
581
 
447
- def run
448
- @run_called = true
582
+ def run(server)
583
+ @run_called_for = server
449
584
  end
450
585
  end
451
586