pigeon 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,4 +1,5 @@
1
1
  *.gem
2
+ *.log
2
3
 
3
4
  .DS_Store
4
5
  *.tmproj
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.3.0
data/bin/launcher.example CHANGED
@@ -1,54 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- COMMAND_NAME = 'launcher'
4
- engine = engine
3
+ $LOAD_PATH << File.expand_path(File.join(*%w[ .. lib ]), File.dirname(__FILE__))
4
+ require 'pigeon'
5
5
 
6
- options = {
7
- :dir => engine.pid_dir,
8
- :debug => true, # ((ENV['RAILS_ENV'] == 'production') ? ENV['PINGITY_DEBUG'] : true),
9
- :modules => [ ]
10
- }
11
-
12
- begin
13
- case (command)
14
- when 'start'
15
- engine.start(options) do |pid|
16
- puts "Pigeon Engine now running. [%d]" % pid
17
- end
18
- when 'stop'
19
- engine.stop(options) do |pid|
20
- if (pid)
21
- puts "Pigeon Engine shut down. [%d]" % pid
22
- else
23
- puts "Pigeon Engine was not running."
24
- end
25
- end
26
- when 'restart'
27
- engine.restart(options) do |old_pid, new_pid|
28
- if (old_pid)
29
- puts "Pigeon Engine terminated. [%d]" % old_pid
30
- end
31
- puts "Pigeon Engine now running. [%d]" % new_pid
32
- end
33
- when 'status'
34
- engine.status(options) do |pid|
35
- if (pid)
36
- puts "Pigeon Engine running. [%d]" % pid
37
- else
38
- puts "Pigeon Engine is not running."
39
- end
40
- end
41
- when 'run'
42
- options[:logger] = Pigeon::Logger.new(STDOUT)
43
-
44
- engine.run(options) do |pid|
45
- puts "Pigeon Engine now running. [%d]" % pid
46
- puts "Use ^C to terminate."
47
- end
48
- else
49
- puts "Usage: #{COMMAND_NAME} [start|stop|restart|status|run]"
50
- end
51
- rescue Interrupt
52
- puts "Shutting down."
53
- exit(0)
54
- end
6
+ Pigeon::Launcher.new(Pigeon::Engine).handle_args(ARGV)
data/lib/pigeon.rb CHANGED
@@ -1,7 +1,12 @@
1
1
  module Pigeon
2
+ # == Autoloads ============================================================
3
+
2
4
  autoload(:Engine, 'pigeon/engine')
3
- autoload(:Queue, 'pigeon/queue')
5
+ autoload(:Launcher, 'pigeon/launcher')
6
+ autoload(:OptionAccessor, 'pigeon/option_accessor')
4
7
  autoload(:Pidfile, 'pigeon/pidfile')
8
+ autoload(:Queue, 'pigeon/queue')
5
9
  autoload(:Support, 'pigeon/support')
6
- autoload(:Logger, 'pigeon/logger')
7
10
  end
11
+
12
+ require 'pigeon/logger'
data/lib/pigeon/engine.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'eventmachine'
2
2
  require 'socket'
3
+ require 'digest/sha1'
3
4
 
4
5
  class Pigeon::Engine
5
6
  # == Submodules ===========================================================
@@ -7,9 +8,43 @@ class Pigeon::Engine
7
8
  class RuntimeError < Exception
8
9
  end
9
10
 
11
+ class ConfigurationError < Exception
12
+ end
13
+
14
+ # == Extensions ==========================================================
15
+
16
+ extend Pigeon::OptionAccessor
17
+
10
18
  # == Properties ===========================================================
11
19
 
12
- attr_reader :logger
20
+ option_accessor :logger
21
+
22
+ option_accessor :name
23
+ option_accessor :pid_file_name
24
+ option_accessor :foreground,
25
+ :boolean => true
26
+ option_accessor :debug,
27
+ :boolean => true
28
+
29
+ option_accessor :engine_log_name,
30
+ :default => 'engine.log'
31
+ option_accessor :engine_logger
32
+
33
+ option_accessor :query_log_name,
34
+ :default => 'query.log'
35
+ option_accessor :query_logger
36
+
37
+ option_accessor :try_pid_dirs,
38
+ :default => %w[
39
+ /var/run
40
+ /tmp
41
+ ].freeze
42
+
43
+ option_accessor :try_log_dirs,
44
+ :default => %w[
45
+ /var/log
46
+ /tmp
47
+ ].freeze
13
48
 
14
49
  # == Constants ============================================================
15
50
 
@@ -20,33 +55,45 @@ class Pigeon::Engine
20
55
  before_stop
21
56
  after_stop
22
57
  ].collect(&:to_sym).freeze
23
-
24
- PID_DIR = [
25
- File.expand_path(File.join(*%w[ .. .. .. .. shared run ]), File.dirname(__FILE__)),
26
- '/var/run',
27
- '/tmp'
28
- ].find { |path| File.exist?(path) and File.writable?(path) }
29
-
30
- LOG_DIR = [
31
- File.expand_path(File.join(*%w[ .. .. .. .. shared log ] ), File.dirname(__FILE__)),
32
- File.expand_path(File.join(*%w[ .. .. log ]), File.dirname(__FILE__)),
33
- '/tmp'
34
- ].find { |path| File.exist?(path) and File.writable?(path) }
35
-
36
- DEFAULT_OPTIONS = {
37
- :pid_file => File.expand_path('pigeon-engine.pid', PID_DIR)
38
- }.freeze
39
58
 
40
59
  # == Class Methods ========================================================
41
60
 
42
- def self.options_with_defaults(options = nil)
43
- options ? DEFAULT_OPTIONS.merge(options) : DEFAULT_OPTIONS
61
+ # Returns the human-readable name of this engine. Defaults to the name
62
+ # of the engine class, but can be replaced to customize a subclass.
63
+ def self.name
64
+ @name or self.to_s.gsub(/::/, ' ')
65
+ end
66
+
67
+ # Returns the name of the PID file to use. The full path to the file
68
+ # is specified elsewhere.
69
+ def self.pid_file_name
70
+ @pid_file_name or self.name.downcase.gsub(/ /, '-') + '.pid'
71
+ end
72
+
73
+ # Returns the full path to the PID file that should be used to track
74
+ # the running status of this engine.
75
+ def self.pid_file_path
76
+ @pid_file_path ||= begin
77
+ if (path = Pigeon::Support.find_writable_directory(self.try_pid_dirs))
78
+ File.expand_path(self.pid_file_name, path)
79
+ else
80
+ raise ConfigurationError, "Could not find a writable directory for the PID file in: #{self.try_pid_dirs.join(' ')}"
81
+ end
82
+ end
44
83
  end
45
84
 
46
- def self.launch_with_options(options = nil)
85
+ # Returns the full path to the directory used to store logs.
86
+ def self.log_dir
87
+ @log_file_path ||= Pigeon::Support.find_writable_directory(self.try_log_dirs)
88
+ end
89
+
90
+ # Launches the engine with the specified options
91
+ def self.launch(options = nil)
47
92
  EventMachine.run do
48
- engine = new(options_with_defaults(options))
49
-
93
+ engine = new(options)
94
+
95
+ yield(engine) if (block_given?)
96
+
50
97
  Signal.trap('INT') do
51
98
  engine.terminate
52
99
  end
@@ -55,35 +102,30 @@ class Pigeon::Engine
55
102
  end
56
103
  end
57
104
 
58
- def self.pid_dir
59
- PID_DIR
60
- end
61
-
62
- def self.pid_file(options = nil)
63
- Pigeon::Pidfile.new(options_with_defaults(options)[:pid_file])
105
+ def self.pid_file
106
+ @pid_file ||= Pigeon::Pidfile.new(self.pid_file_path)
64
107
  end
65
108
 
66
109
  def self.start(options = nil)
67
110
  pid = Pigeon::Support.daemonize do
68
- launch_with_options(options)
111
+ launch(options)
69
112
  end
70
113
 
71
- pid_file(options).create!(pid)
114
+ pid_file.create!(pid)
72
115
 
73
116
  yield(pid) if (block_given?)
74
117
 
75
118
  pid
76
119
  end
77
120
 
78
- def self.run(options = nil)
121
+ def self.run
79
122
  yield($$) if (block_given?)
80
123
 
81
- launch_with_options((options || { }).merge(:foreground => true))
124
+ launch(:foreground => true)
82
125
  end
83
126
 
84
- def self.stop(options = nil)
85
- pf = pid_file(options)
86
- pid = pf.running
127
+ def self.stop
128
+ pid = self.pid_file.running
87
129
 
88
130
  if (pid)
89
131
  begin
@@ -92,7 +134,8 @@ class Pigeon::Engine
92
134
  # No such process exception
93
135
  pid = nil
94
136
  end
95
- pf.remove!
137
+
138
+ pid_file.remove!
96
139
  end
97
140
 
98
141
  pid = pid.to_i if (pid)
@@ -102,28 +145,41 @@ class Pigeon::Engine
102
145
  pid
103
146
  end
104
147
 
105
- def self.restart(options = nil)
106
- self.stop(options)
107
- self.start(options)
148
+ def self.restart
149
+ self.stop
150
+ self.start
108
151
  end
109
152
 
110
- def self.status(options = nil)
111
- pid = pid_file(options).running
153
+ def self.running?
154
+ pid_file.running
155
+ end
156
+
157
+ def self.status
158
+ pid = pid_file.running
112
159
 
113
160
  yield(pid) if (block_given?)
114
161
 
115
162
  pid
116
163
  end
164
+
165
+ # Returns a default logger for the engine.
166
+ def self.engine_logger
167
+ @engine_logger ||= begin
168
+ f = File.open(File.expand_path(self.engine_log_name, self.log_dir), 'w+')
169
+ f.sync = true
117
170
 
118
- def self.sql_logger
119
- f = File.open(File.expand_path("query.log", LOG_DIR), 'w+')
120
- f.sync = true
121
-
122
- Pigeon::Logger.new(f)
171
+ Pigeon::Logger.new(f)
172
+ end
123
173
  end
124
-
125
- def self.log_dir
126
- LOG_DIR
174
+
175
+ # Returns a default logger for queries.
176
+ def self.query_logger
177
+ @query_logger ||= begin
178
+ f = File.open(File.expand_path(self.query_log_name, self.log_dir), 'w+')
179
+ f.sync = true
180
+
181
+ Pigeon::Logger.new(f)
182
+ end
127
183
  end
128
184
 
129
185
  # == Instance Methods =====================================================
@@ -131,35 +187,47 @@ class Pigeon::Engine
131
187
  def initialize(options = nil)
132
188
  @options = options || { }
133
189
 
134
- @task_lock = { }
190
+ @task_lock = Mutex.new
191
+ @task_locks = { }
135
192
 
136
- @logger = @options[:logger] || Pigeon::Logger.new(File.open(File.expand_path('engine.log', LOG_DIR), 'w+'))
137
-
138
- @logger.level = Pigeon::Logger::DEBUG if (@options[:debug])
193
+ self.logger ||= self.engine_logger
194
+ self.logger.level = Pigeon::Logger::DEBUG if (self.debug?)
139
195
 
140
196
  @queue = { }
141
197
 
142
198
  run_chain(:after_initialize)
143
199
  end
144
200
 
201
+ # Returns the hostname of the system this engine is running on.
202
+ def host
203
+ Socket.gethostname
204
+ end
205
+
206
+ # Returns a unique 160-bit identifier for this engine expressed as a 40
207
+ # character hexadecimal string. The first 32-bit sequence is a timestamp
208
+ # so these numbers increase over time and can be used to identify when
209
+ # a particular instance was launched.
210
+ def id
211
+ @id ||= '%8x%s' % [
212
+ Time.now.to_i,
213
+ Digest::SHA1.hexdigest(
214
+ '%.8f%8x' % [ Time.now.to_f, rand(1 << 32) ]
215
+ )[0, 32]
216
+ ]
217
+ end
218
+
219
+ # Handles the run phase of the engine, triggers the before_start and
220
+ # after_start events accordingly.
145
221
  def run
146
222
  run_chain(:before_start)
147
223
 
148
224
  STDOUT.sync = true
149
225
 
150
- @logger.info("Engine \##{id} Running")
226
+ logger.info("Engine \##{id} Running")
151
227
 
152
228
  run_chain(:after_start)
153
229
  end
154
230
 
155
- def host
156
- Socket.gethostname
157
- end
158
-
159
- def id
160
- @id ||= '%8x%8x' % [ Time.now.to_i, rand(1 << 32) ]
161
- end
162
-
163
231
  # Used to periodically execute a task or block. When giving a task name,
164
232
  # a method by that name is called, otherwise a block must be supplied.
165
233
  # An interval can be specified in seconds, or will default to 1.
@@ -177,19 +245,20 @@ class Pigeon::Engine
177
245
  end
178
246
  end
179
247
 
248
+ # This is a somewhat naive locking mechanism that may break down
249
+ # when two requests are fired off within a nearly identical period.
250
+ # For now, this achieves a general purpose solution that should work
251
+ # under most circumstances. Refactor later to improve.
180
252
  def task_lock(task_name)
181
- # NOTE: This is a somewhat naive locking mechanism that may break down
182
- # when two requests are fired off within a nearly identical period.
183
- # For now, this achieves a general purpose solution that should work
184
- # under most circumstances. Refactor later to improve.
185
-
186
- return if (@task_lock[task_name])
187
-
188
- @task_lock[task_name] = true
253
+ @task_lock.synchronize do
254
+ @task_locks[task_name] ||= Mutex.new
255
+ end
189
256
 
190
- yield if (block_given?)
257
+ return if (@task_locks[task_name].locked?)
191
258
 
192
- @task_lock[task_name] = false
259
+ @task_lock[task_name].synchronize do
260
+ yield if (block_given?)
261
+ end
193
262
  end
194
263
 
195
264
  def timer(interval, &block)
@@ -202,21 +271,27 @@ class Pigeon::Engine
202
271
  EventMachine::PeriodicTimer.new(interval, &block)
203
272
  end
204
273
 
205
- # Used to defer a block of work for near-immediate execution. Uses the
206
- # EventMachine#defer method but is not as efficient as the queue method.
274
+ # Used to defer a block of work for near-immediate execution. Is a
275
+ # wrapper around EventMachine#defer and does not perform as well as using
276
+ # the alternate queue method.
207
277
  def defer(&block)
208
278
  EventMachine.defer(&block)
209
279
  end
210
280
 
211
- # Shuts down the engine.
281
+ # Shuts down the engine. Will also trigger the before_stop and after_stop
282
+ # events.
212
283
  def terminate
213
284
  run_chain(:before_stop)
285
+
214
286
  EventMachine.stop_event_loop
287
+
215
288
  run_chain(:after_stop)
216
289
  end
217
290
 
218
291
  # Used to queue a block for immediate processing on a background thread.
219
- # An optional queue name can be used to sequence tasks properly.
292
+ # An optional queue name can be used to sequence tasks properly. The main
293
+ # queue has a large number of threads, while the named queues default
294
+ # to only one so they can be processed sequentially.
220
295
  def queue(name = :default, &block)
221
296
  target_queue = @queue[name] ||= Pigeon::Queue.new(name == :default ? nil : 1)
222
297
 
@@ -251,10 +326,12 @@ class Pigeon::Engine
251
326
  end
252
327
  end
253
328
 
329
+ # Returns true if the debug option was set, false otherwise.
254
330
  def debug?
255
331
  !!@options[:debug]
256
332
  end
257
333
 
334
+ # Returns true if running in the foreground, false otherwise.
258
335
  def foreground?
259
336
  !!@options[:foreground]
260
337
  end
@@ -0,0 +1,79 @@
1
+ class Pigeon::Launcher
2
+ # == Class Methods ========================================================
3
+
4
+ def self.launch(engine, options)
5
+ end
6
+
7
+ # == Instance Methods =====================================================
8
+
9
+ def initialize(with_engine = Pigeon::Engine)
10
+ @engine = with_engine
11
+ end
12
+
13
+ def handle_args(*args)
14
+ command = args.flatten.first
15
+
16
+ begin
17
+ case (command)
18
+ when 'start'
19
+ @engine.start(&method(:start))
20
+ when 'stop'
21
+ @engine.stop(&method(:stop))
22
+ when 'restart'
23
+ @engine.restart(&method(:restart))
24
+ when 'status'
25
+ @engine.status(&method(:status))
26
+ when 'run'
27
+ @engine.logger = Pigeon::Logger.new(STDOUT)
28
+
29
+ @engine.run(&method(:run))
30
+ else
31
+ usage
32
+ end
33
+ rescue Interrupt
34
+ shutdown(pid)
35
+ exit(0)
36
+ end
37
+ end
38
+
39
+ def run(pid)
40
+ puts "#{@engine.name} now running. [%d]" % pid
41
+ puts "Use ^C to terminate."
42
+ end
43
+
44
+ def start(pid)
45
+ puts "#{@engine.name} now running. [%d]" % pid
46
+ end
47
+
48
+ def stop(pid)
49
+ if (pid)
50
+ puts "#{@engine.name} shut down. [%d]" % pid
51
+ else
52
+ puts "#{@engine.name} was not running."
53
+ end
54
+ end
55
+
56
+ def status(pid)
57
+ if (pid)
58
+ puts "#{@engine.name} running. [%d]" % pid
59
+ else
60
+ puts "#{@engine.name} is not running."
61
+ end
62
+ end
63
+
64
+ def restart(pid, old_pid)
65
+ if (old_pid)
66
+ puts "#{@engine.name} terminated. [%d]" % old_pid
67
+ end
68
+
69
+ puts "#{@engine.name} now running. [%d]" % pid
70
+ end
71
+
72
+ def shutdown(pid)
73
+ puts "Shutting down."
74
+ end
75
+
76
+ def usage
77
+ puts "Usage: #{File.basename($0)} [start|stop|restart|status|run]"
78
+ end
79
+ end
@@ -0,0 +1,95 @@
1
+ module Pigeon::OptionAccessor
2
+ # Given a list of names, this declares an option accessor which works like
3
+ # a combination of cattr_accessor and attr_accessor, except that defaults
4
+ # defined for a class will propagate down to the instances and subclasses,
5
+ # but these defaults can be over-ridden in subclasses and instances
6
+ # without interference. Optional hash at end of list can be used to set:
7
+ # * :default => Assigns a default value which is otherwise nil
8
+ def option_accessor(*args)
9
+ option_reader(*args)
10
+ option_writer(*args)
11
+ end
12
+
13
+ # Given a list of names, this declares an option reader which works like
14
+ # a combination of cattr_reader and attr_reader, except that defaults
15
+ # defined for a class will propagate down to the instances and subclasses,
16
+ # but these defaults can be over-ridden in subclasses and instances
17
+ # without interference. Optional hash at end of list can be used to set:
18
+ # * :default => Assigns a default value which is otherwise nil
19
+ def option_reader(*names)
20
+ names = [ names ].flatten.compact
21
+ options = names.last.is_a?(Hash) ? names.pop : { }
22
+
23
+ names.each do |name|
24
+ iv = :"@#{name}"
25
+
26
+ (class << self; self; end).class_eval do
27
+ if (options[:boolean])
28
+ define_method(:"#{name}?") do
29
+ iv_value = instance_variable_get(iv)
30
+
31
+ !!(iv_value.nil? ? (self.superclass.respond_to?(name) ? self.superclass.send(name) : nil) : iv_value)
32
+ end
33
+ end
34
+
35
+ define_method(name) do
36
+ iv_value = instance_variable_get(iv)
37
+
38
+ iv_value.nil? ? (self.superclass.respond_to?(name) ? self.superclass.send(name) : nil) : iv_value
39
+ end
40
+ end
41
+
42
+ define_method(name) do
43
+ iv_value = instance_variable_get(iv)
44
+
45
+ iv_value.nil? ? self.class.send(name) : iv_value
46
+ end
47
+
48
+ if (options[:boolean])
49
+ define_method(:"#{name}?") do
50
+ iv_value = instance_variable_get(iv)
51
+
52
+ !!(iv_value.nil? ? self.class.send(name) : iv_value)
53
+ end
54
+ end
55
+
56
+ instance_variable_set(iv, options[:default])
57
+ end
58
+ end
59
+
60
+ # Given a list of names, this declares an option writer which works like
61
+ # a combination of cattr_writer and attr_writer, except that defaults
62
+ # defined for a class will propagate down to the instances and subclasses,
63
+ # but these defaults can be over-ridden in subclasses and instances
64
+ # without interference.
65
+ def option_writer(*names)
66
+ names = [ names ].flatten.compact
67
+ options = names.last.is_a?(Hash) ? names.pop : { }
68
+
69
+ names.each do |name|
70
+ iv = :"@#{name}"
71
+
72
+ (class << self; self; end).class_eval do
73
+ if (options[:boolean])
74
+ define_method(:"#{name}=") do |value|
75
+ instance_variable_set(iv, value.nil? ? nil : !!value)
76
+ end
77
+ else
78
+ define_method(:"#{name}=") do |value|
79
+ instance_variable_set(iv, value)
80
+ end
81
+ end
82
+ end
83
+
84
+ if (options[:boolean])
85
+ define_method(:"#{name}=") do |value|
86
+ instance_variable_set(iv, value.nil? ? nil : !!value)
87
+ end
88
+ else
89
+ define_method(:"#{name}=") do |value|
90
+ instance_variable_set(iv, value)
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -1,5 +1,8 @@
1
1
  module Pigeon::Support
2
- def self.daemonize
2
+ # Uses the double-fork method to create a fully detached background
3
+ # process. Returns the process ID of the created process. May throw an
4
+ # exception if these processes could not be created.
5
+ def daemonize
3
6
  rfd, wfd = IO.pipe
4
7
 
5
8
  forked_pid = fork do
@@ -18,4 +21,15 @@ module Pigeon::Support
18
21
 
19
22
  daemon_pid.to_i
20
23
  end
24
+
25
+ # Finds the first directory in the given list that exists and is
26
+ # writable. Returns nil if none match.
27
+ def find_writable_directory(*list)
28
+ list.flatten.compact.find do |dir|
29
+ File.exist?(dir) and File.writable?(dir)
30
+ end
31
+ end
32
+
33
+ # Make all methods callable directly without having to include it
34
+ extend self
21
35
  end
data/pigeon.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{pigeon}
8
- s.version = "0.2.0"
8
+ s.version = "0.3.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["tadman"]
12
- s.date = %q{2010-10-05}
12
+ s.date = %q{2010-10-19}
13
13
  s.default_executable = %q{launcher.example}
14
14
  s.description = %q{Pigeon is a simple way to get started building an EventMachine engine that's intended to run as a background job.}
15
15
  s.email = %q{github@tadman.ca}
@@ -28,7 +28,9 @@ Gem::Specification.new do |s|
28
28
  "bin/launcher.example",
29
29
  "lib/pigeon.rb",
30
30
  "lib/pigeon/engine.rb",
31
+ "lib/pigeon/launcher.rb",
31
32
  "lib/pigeon/logger.rb",
33
+ "lib/pigeon/option_accessor.rb",
32
34
  "lib/pigeon/pidfile.rb",
33
35
  "lib/pigeon/queue.rb",
34
36
  "lib/pigeon/support.rb",
@@ -36,6 +38,8 @@ Gem::Specification.new do |s|
36
38
  "test/helper.rb",
37
39
  "test/test_pigeon.rb",
38
40
  "test/test_pigeon_engine.rb",
41
+ "test/test_pigeon_launcher.rb",
42
+ "test/test_pigeon_option_accessor.rb",
39
43
  "test/test_pigeon_queue.rb"
40
44
  ]
41
45
  s.homepage = %q{http://github.com/tadman/pigeon}
@@ -47,6 +51,8 @@ Gem::Specification.new do |s|
47
51
  "test/helper.rb",
48
52
  "test/test_pigeon.rb",
49
53
  "test/test_pigeon_engine.rb",
54
+ "test/test_pigeon_launcher.rb",
55
+ "test/test_pigeon_option_accessor.rb",
50
56
  "test/test_pigeon_queue.rb"
51
57
  ]
52
58
 
data/test/helper.rb CHANGED
@@ -4,8 +4,15 @@ require 'test/unit'
4
4
  $LOAD_PATH.unshift(File.expand_path(*%w[ .. lib ]), File.dirname(__FILE__))
5
5
  $LOAD_PATH.unshift(File.dirname(__FILE__))
6
6
 
7
+ require 'rubygems'
8
+
9
+ if (Gem.available?('eventmachine'))
10
+ gem 'eventmachine'
11
+ else
12
+ raise "EventMachine gem is not installed."
13
+ end
14
+
7
15
  require 'pigeon'
8
16
 
9
17
  class Test::Unit::TestCase
10
- # ...
11
18
  end
@@ -28,35 +28,39 @@ end
28
28
 
29
29
  class CallbackTestEngine < Pigeon::Engine
30
30
  after_initialize do
31
- self.track(:after_initialize)
31
+ track(:after_initialize)
32
32
  end
33
33
 
34
34
  before_start do
35
- self.track(:before_start)
35
+ track(:before_start)
36
36
  end
37
37
 
38
38
  after_start do
39
- self.track(:after_start)
39
+ track(:after_start)
40
40
  end
41
41
 
42
42
  before_stop do
43
- self.track(:before_stop)
43
+ track(:before_stop)
44
44
  end
45
45
 
46
46
  after_stop do
47
- self.track(:after_stop)
47
+ track(:after_stop)
48
+
48
49
  @options[:pipe].close
49
50
  end
50
51
 
51
52
  def track(callback)
52
- pipe = @options[:pipe]
53
-
54
- pipe.puts(callback.to_s)
55
- pipe.flush
53
+ @options[:pipe].puts(callback.to_s)
54
+ @options[:pipe].flush
56
55
  end
57
56
  end
58
57
 
59
58
  class TestPigeonEngine < Test::Unit::TestCase
59
+ def test_default_options
60
+ assert TestEngine.engine_logger
61
+ assert TestEngine.engine_logger.is_a?(Logger)
62
+ end
63
+
60
64
  def test_example_subclass
61
65
  engine_pid = nil
62
66
 
@@ -122,10 +126,15 @@ class TestPigeonEngine < Test::Unit::TestCase
122
126
 
123
127
  write_fd.close
124
128
 
129
+ reported_status = false
130
+
125
131
  CallbackTestEngine.status do |pid|
126
132
  assert_equal engine_pid, pid
133
+ reported_status = true
127
134
  end
128
135
 
136
+ assert reported_status
137
+
129
138
  CallbackTestEngine.stop do |pid|
130
139
  assert_equal engine_pid, pid
131
140
  end
@@ -0,0 +1,51 @@
1
+ require File.expand_path(File.join(*%w[ helper ]), File.dirname(__FILE__))
2
+
3
+ class PigeonLauncherTest < Test::Unit::TestCase
4
+ def test_default_launcher
5
+ pid = Pigeon::Launcher.launch
6
+
7
+ assert pid
8
+ assert Pigeon::Engine.running?
9
+
10
+ Pigeon::Launcher.stop
11
+
12
+ assert !Pigeon::Engine.running?
13
+ end
14
+
15
+ def test_triggers
16
+ triggered = Hash.new do |h, k|
17
+ h[k] = 0
18
+ end
19
+
20
+ Pigeon::Launcher.new(Pigeon::Engine).handle_args('start') do
21
+ start do
22
+ triggered[:start] += 1
23
+ end
24
+ end
25
+
26
+ Pigeon::Launcher.new(Pigeon::Engine).handle_args('restart') do
27
+ restart do
28
+ triggered[:restart] += 1
29
+ end
30
+ end
31
+
32
+ Pigeon::Launcher.new(Pigeon::Engine).handle_args('status') do
33
+ status do
34
+ triggered[:status] += 1
35
+ end
36
+ end
37
+
38
+ Pigeon::Launcher.new(Pigeon::Engine).handle_args('stop') do
39
+ stop do
40
+ triggered[:stop] += 1
41
+ end
42
+ end
43
+
44
+ # FIX: Test `run`
45
+
46
+ assert_equal 1, triggered[:start]
47
+ assert_equal 1, triggered[:restart]
48
+ assert_equal 1, triggered[:status]
49
+ assert_equal 1, triggered[:stop]
50
+ end
51
+ end
@@ -0,0 +1,71 @@
1
+ require File.expand_path(File.join(*%w[ helper ]), File.dirname(__FILE__))
2
+
3
+ class OptionClass
4
+ extend Pigeon::OptionAccessor
5
+
6
+ option_accessor :single_option
7
+ option_accessor :multi1, :multi2
8
+ option_accessor :option_with_default,
9
+ :default => :example_default
10
+
11
+ option_accessor :boolean_option,
12
+ :boolean => true
13
+
14
+ self.single_option = :single_option_default
15
+ end
16
+
17
+ class OptionSubclass < OptionClass
18
+ end
19
+
20
+ class PigeonOptionAccessorTest < Test::Unit::TestCase
21
+ def test_class_and_instance_chaining
22
+ assert_equal :single_option_default, OptionClass.single_option
23
+
24
+ instance = OptionClass.new
25
+
26
+ assert_equal :single_option_default, instance.single_option
27
+
28
+ OptionClass.single_option = :new_default
29
+
30
+ assert_equal :new_default, instance.single_option
31
+
32
+ instance.single_option = :override
33
+
34
+ assert_equal :override, instance.single_option
35
+ assert_equal :new_default, OptionClass.single_option
36
+
37
+ # Reset to defaults for next test
38
+ OptionClass.single_option = :single_option_default
39
+ end
40
+
41
+ def test_subclass_inheritance
42
+ assert_equal :single_option_default, OptionSubclass.single_option
43
+
44
+ OptionSubclass.single_option = :subclass_default
45
+
46
+ assert_equal :subclass_default, OptionSubclass.single_option
47
+ assert_equal :single_option_default, OptionClass.single_option
48
+
49
+ class_instance = OptionClass.new
50
+ subclass_instance = OptionSubclass.new
51
+
52
+ assert_equal :subclass_default, subclass_instance.single_option
53
+ assert_equal :single_option_default, class_instance.single_option
54
+
55
+ # Reset to defaults for next test
56
+ OptionSubclass.single_option = nil
57
+ end
58
+
59
+ def test_boolean_option
60
+ assert_equal nil, OptionClass.multi1
61
+
62
+ instance = OptionClass.new
63
+
64
+ instance.multi1 = false
65
+
66
+ assert_equal false, instance.multi1
67
+ assert_equal nil, OptionClass.multi1
68
+
69
+ assert_equal nil, instance.multi2
70
+ end
71
+ end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 2
7
+ - 3
8
8
  - 0
9
- version: 0.2.0
9
+ version: 0.3.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - tadman
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-10-05 00:00:00 -04:00
17
+ date: 2010-10-19 00:00:00 -04:00
18
18
  default_executable: launcher.example
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -49,7 +49,9 @@ files:
49
49
  - bin/launcher.example
50
50
  - lib/pigeon.rb
51
51
  - lib/pigeon/engine.rb
52
+ - lib/pigeon/launcher.rb
52
53
  - lib/pigeon/logger.rb
54
+ - lib/pigeon/option_accessor.rb
53
55
  - lib/pigeon/pidfile.rb
54
56
  - lib/pigeon/queue.rb
55
57
  - lib/pigeon/support.rb
@@ -57,6 +59,8 @@ files:
57
59
  - test/helper.rb
58
60
  - test/test_pigeon.rb
59
61
  - test/test_pigeon_engine.rb
62
+ - test/test_pigeon_launcher.rb
63
+ - test/test_pigeon_option_accessor.rb
60
64
  - test/test_pigeon_queue.rb
61
65
  has_rdoc: true
62
66
  homepage: http://github.com/tadman/pigeon
@@ -94,4 +98,6 @@ test_files:
94
98
  - test/helper.rb
95
99
  - test/test_pigeon.rb
96
100
  - test/test_pigeon_engine.rb
101
+ - test/test_pigeon_launcher.rb
102
+ - test/test_pigeon_option_accessor.rb
97
103
  - test/test_pigeon_queue.rb