foreground 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -27,7 +27,7 @@ Install it via RubyGems:
27
27
 
28
28
  Wrap your daemon command inside your launchd.plist:
29
29
 
30
- foreground --pid_file /tmp/foreground_sample_daemon.pid foreground_sample_daemon
30
+ foreground --pid_file /tmp/foreground_sample_daemon.pid --command "foreground_sample_daemon --with arguments"
31
31
 
32
32
  ## Limitations
33
33
 
@@ -1,4 +1,4 @@
1
- Feature: Start and stop
1
+ Feature: Start and stop daemon
2
2
 
3
3
  In order to start and stop a background daemon
4
4
  As a funky user
@@ -40,7 +40,7 @@ end
40
40
 
41
41
  When /^I run the sample daemon via foreground$/ do
42
42
  @foreground = fork do
43
- exec('foreground', '--pid_file', '/tmp/foreground_sample_daemon.pid', 'foreground_sample_daemon')
43
+ exec('foreground', '--pid_file', '/tmp/foreground_sample_daemon.pid', '--command', 'foreground_sample_daemon')
44
44
  end
45
45
  end
46
46
 
@@ -0,0 +1,14 @@
1
+ Feature: Watch daemon
2
+
3
+ In order to notify launchd about a terminated daemon
4
+ As a funky user
5
+ I want foreground to terminate as well
6
+
7
+ Scenario: Terminate when daemon goes down
8
+ Given I run the sample daemon via foreground
9
+ Then foreground should run
10
+ And the sample daemon should run
11
+ Given I run `sleep 2`
12
+ When I kill the sample daemon
13
+ Then foreground should not run
14
+ And the sample daemon should not run
@@ -1,8 +1,28 @@
1
+ require 'logger'
1
2
  require 'mixlib/cli'
2
3
  require 'foreground/version'
3
4
  require 'foreground/daemon'
4
5
  require 'foreground/cli'
5
6
 
6
7
  module Foreground
7
- # Your code goes here... or not.
8
+ class << self
9
+ # The global configuration based on command line options.
10
+ attr_accessor :config
11
+ end
12
+
13
+ def logger
14
+ @logger ||= create_logger
15
+ end
16
+
17
+ private
18
+
19
+ def create_logger
20
+ STDOUT.sync = true
21
+ l = Logger.new(STDOUT)
22
+ #TODO: Test this!
23
+ l.formatter = proc do |severity, datetime, progname, msg|
24
+ "#{progname} [#{severity}]: #{msg}\n"
25
+ end
26
+ l
27
+ end
8
28
  end
@@ -8,6 +8,20 @@ module Foreground
8
8
  :description => 'PID file for the daemon',
9
9
  :required => true
10
10
 
11
+ option :command,
12
+ :short => '-c COMMAND',
13
+ :long => '--command COMMAND',
14
+ :description => 'Daemon command line',
15
+ :required => true
16
+
17
+ option :timeout,
18
+ :short => '-t SECONDS',
19
+ :long => '--timeout SECONDS',
20
+ :description => 'Timeout for the daemon to generate a valid PID file',
21
+ :required => false,
22
+ :default => 2,
23
+ :proc => Proc.new { |t| t.to_i }
24
+
11
25
  class << self
12
26
  def run(argv=ARGV)
13
27
  new.run(argv)
@@ -16,7 +30,8 @@ module Foreground
16
30
 
17
31
  def run(argv)
18
32
  cmd = parse_options(argv)
19
- Daemon.run(cmd, config[:pid_file])
33
+ Foreground.config = config
34
+ Daemon.run(config[:command], config[:pid_file])
20
35
  end
21
36
  end
22
37
  end
@@ -1,6 +1,8 @@
1
1
  require 'foreground'
2
2
 
3
3
  module Foreground
4
+ class DaemonError < StandardError; end
5
+
4
6
  class Daemon
5
7
  @daemon = nil
6
8
 
@@ -34,15 +36,35 @@ module Foreground
34
36
  Process.kill(signal, pid)
35
37
  end
36
38
 
39
+ #TODO: Add scenario for this stuff!
37
40
  def pid
38
- #TODO: Replace sleep with timeout!
39
- sleep 0.1 # Give the daemon time to write its PID file.
40
- File.read(@pid_file).chomp.to_i
41
+ return @pid unless @pid.nil?
42
+ elapsed_time = 0
43
+ sleep_time = 0.1
44
+ begin
45
+ break if @pid = read_pid
46
+ rescue
47
+ raise unless elapsed_time < Foreground.config[:timeout]
48
+ elapsed_time += sleep_time
49
+ end while sleep(sleep_time)
50
+ @pid
41
51
  end
42
52
 
43
53
  def watch
44
- #TODO: Implement watch feature!
45
- loop { sleep 1 }
54
+ watch_interval = 10
55
+ begin
56
+ kill(0)
57
+ rescue Errno::ESRCH
58
+ raise DaemonError, "No process with PID #{pid} found."
59
+ end while sleep(watch_interval)
60
+ end
61
+
62
+ private
63
+
64
+ def read_pid
65
+ pid = File.read(@pid_file).to_i
66
+ raise DaemonError, "PID not readable from #{@pid_file}" unless pid > 0
67
+ pid
46
68
  end
47
69
  end
48
70
  end
@@ -1,7 +1,7 @@
1
1
  module Foreground
2
2
  [:TERM, :INT].each do |signal|
3
3
  trap(signal) do
4
- Daemon.kill(:TERM)
4
+ Daemon.kill(:TERM) rescue Errno::ESRCH
5
5
  exit
6
6
  end
7
7
  end
@@ -1,3 +1,3 @@
1
1
  module Foreground
2
- VERSION = '0.0.4'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -4,7 +4,8 @@ module Foreground
4
4
  describe CLI do
5
5
  before do
6
6
  Daemon.stub(:run)
7
- @argv = ['--pid_file', '/tmp/foreground_sample_daemon.pid', 'foreground_sample_daemon']
7
+ @cmd = 'foreground_sample_daemon --with "some arguments"'
8
+ @argv = ['--pid_file', '/tmp/foreground_sample_daemon.pid', '--command', @cmd]
8
9
  @cli = CLI.new
9
10
  end
10
11
 
@@ -23,8 +24,13 @@ module Foreground
23
24
  @cli.run(@argv)
24
25
  end
25
26
 
27
+ it 'should make the config globally available' do
28
+ @cli.run(@argv)
29
+ Foreground.config.should eql(@cli.config)
30
+ end
31
+
26
32
  it 'should run the daemon' do
27
- Daemon.should_receive(:run).with(['foreground_sample_daemon'], '/tmp/foreground_sample_daemon.pid')
33
+ Daemon.should_receive(:run).with(@cmd, '/tmp/foreground_sample_daemon.pid')
28
34
  @cli.run(@argv)
29
35
  end
30
36
  end
@@ -4,7 +4,8 @@ module Foreground
4
4
  describe Daemon do
5
5
  before do
6
6
  Process.stub(:kill)
7
- @cmd = ['some_daemon', '--with', 'arguments']
7
+ #@cmd = ['some_daemon', '--with', 'arguments']
8
+ @cmd = 'some_daemon --with "many arguments"'
8
9
  @pid_file = 'some_daemon.pid'
9
10
  @pid = 42
10
11
  @args = [@cmd, @pid_file]
@@ -34,12 +35,31 @@ module Foreground
34
35
 
35
36
  describe '#run' do
36
37
  it 'should run and watch the daemon' do
37
- @daemon.should_receive(:system).with(*@cmd).ordered
38
+ @daemon.should_receive(:system).with(@cmd).ordered
38
39
  @daemon.should_receive(:watch).ordered
39
40
  @daemon.run
40
41
  end
41
42
  end
42
43
 
44
+ describe '#watch' do
45
+ before do
46
+ @daemon = Daemon.new(@cmd, @pid_file)
47
+ @daemon.stub(:pid).and_return(@pid)
48
+ end
49
+
50
+ it 'should sleep if the daemon is still alive' do
51
+ @daemon.should_receive(:sleep).with(10).and_return(nil)
52
+ Process.should_receive(:kill).with(0, @pid).and_return(1)
53
+ @daemon.watch
54
+ end
55
+
56
+ it 'should raise error if the daemon is not alive' do
57
+ @daemon.should_not_receive(:sleep)
58
+ Process.should_receive(:kill).with(0, @pid).and_raise(Errno::ESRCH)
59
+ lambda { @daemon.watch }.should raise_error(DaemonError, /no process with pid #{@pid} found/i)
60
+ end
61
+ end
62
+
43
63
  describe '#kill' do
44
64
  it 'should send the daemon a SIGTERM' do
45
65
  @daemon.stub(:pid).and_return(42)
@@ -48,10 +68,42 @@ module Foreground
48
68
  end
49
69
  end
50
70
 
71
+ describe '#read_pid' do
72
+ context 'with existing PID file' do
73
+ it 'should return the PID' do
74
+ File.should_receive(:read).with(@pid_file).and_return("#{@pid}\n")
75
+ @daemon.send(:read_pid).should eql(@pid)
76
+ end
77
+ end
78
+
79
+ context 'with unreadable PID' do
80
+ it 'should raise error' do
81
+ File.should_receive(:read).with(@pid_file).and_return('fortytwo')
82
+ lambda { @daemon.send(:read_pid) }.should raise_error(DaemonError, /pid not readable from #{@pid_file}/i)
83
+ end
84
+ end
85
+ end
86
+
51
87
  describe '#pid' do
52
- it 'should return the daemons PID by PID file' do
53
- File.should_receive(:read).with(@pid_file).and_return("#{@pid}\n")
54
- @daemon.pid.should eql(@pid)
88
+ context 'with readable PID' do
89
+ it 'should cache and return the PID' do
90
+ @daemon.should_receive(:read_pid).once.and_return(@pid)
91
+ 2.times { @daemon.pid.should eql(@pid) }
92
+ end
93
+ end
94
+
95
+ context 'with unreadable PID' do
96
+ it 'should rescue and retry after sleep' do
97
+ @daemon.should_receive(:read_pid).and_raise(StandardError)
98
+ @daemon.should_receive(:sleep).with(0.1).and_return(nil)
99
+ @daemon.pid
100
+ end
101
+
102
+ it 'should reraise last exception after 2 seconds' do
103
+ @daemon.stub(:read_pid).and_raise(Errno::ENOENT)
104
+ @daemon.should_receive(:sleep).with(0.1).exactly(20).times.and_return(true)
105
+ lambda { @daemon.pid }.should raise_error(Errno::ENOENT)
106
+ end
55
107
  end
56
108
  end
57
109
  end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ module Foreground
4
+ class Foo
5
+ include Foreground
6
+ end
7
+
8
+ describe '#logger' do
9
+ before do
10
+ @logger = double('logger')
11
+ @logger.stub(:formatter=)
12
+ Logger.stub(:new).and_return(@logger)
13
+ @foo = Foo.new
14
+ end
15
+
16
+ it 'should initialize a new logger once' do
17
+ Logger.should_receive(:new).once.with(STDOUT)
18
+ 2.times { @foo.logger }
19
+ end
20
+
21
+ it 'should return the logger' do
22
+ @foo.logger.should eql(@logger)
23
+ end
24
+
25
+ it 'should flush to stdout' do
26
+ original_sync_state = STDOUT.sync
27
+ STDOUT.sync = false
28
+ STDOUT.sync.should be_false
29
+ @foo.logger
30
+ STDOUT.sync.should be_true
31
+ STDOUT.sync = original_sync_state
32
+ end
33
+ end
34
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreground
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-08 00:00:00.000000000 Z
12
+ date: 2012-09-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: mixlib-cli
@@ -144,6 +144,7 @@ files:
144
144
  - features/start_stop.feature
145
145
  - features/steps/sample_daemon_steps.rb
146
146
  - features/support/env.rb
147
+ - features/watch_daemon.feature
147
148
  - foreground.gemspec
148
149
  - lib/foreground.rb
149
150
  - lib/foreground/cli.rb
@@ -153,6 +154,7 @@ files:
153
154
  - script/bootstrap
154
155
  - spec/foreground/cli_spec.rb
155
156
  - spec/foreground/daemon_spec.rb
157
+ - spec/foreground_spec.rb
156
158
  - spec/spec_helper.rb
157
159
  homepage: https://github.com/bjoernalbers/foreground
158
160
  licenses: []
@@ -168,7 +170,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
168
170
  version: '0'
169
171
  segments:
170
172
  - 0
171
- hash: -3093177780394288803
173
+ hash: 1710979522724105195
172
174
  required_rubygems_version: !ruby/object:Gem::Requirement
173
175
  none: false
174
176
  requirements:
@@ -177,18 +179,20 @@ required_rubygems_version: !ruby/object:Gem::Requirement
177
179
  version: '0'
178
180
  segments:
179
181
  - 0
180
- hash: -3093177780394288803
182
+ hash: 1710979522724105195
181
183
  requirements: []
182
184
  rubyforge_project:
183
185
  rubygems_version: 1.8.24
184
186
  signing_key:
185
187
  specification_version: 3
186
- summary: foreground-0.0.4
188
+ summary: foreground-0.1.0
187
189
  test_files:
188
190
  - features/sample_daemon.feature
189
191
  - features/start_stop.feature
190
192
  - features/steps/sample_daemon_steps.rb
191
193
  - features/support/env.rb
194
+ - features/watch_daemon.feature
192
195
  - spec/foreground/cli_spec.rb
193
196
  - spec/foreground/daemon_spec.rb
197
+ - spec/foreground_spec.rb
194
198
  - spec/spec_helper.rb