pigeon 0.2.0 → 0.3.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/.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