foreground 0.0.4 → 0.1.0

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.
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