daemon_objects 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/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.swp
19
+ bin
20
+ log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm: 1.9.3
3
+ script: bundle exec rake spec
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in daemon_objects.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem 'pry-nav'
8
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Craig Israel
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # DaemonObjects
2
+ [![Build Status](https://travis-ci.org/craigisrael/daemon_objects.png)](https://travis-ci.org/craigisrael/daemon_objects)
3
+ [![Code Climate](https://codeclimate.com/github/craigisrael/daemon_objects.png)](https://codeclimate.com/github/craigisrael/daemon_objects)
4
+
5
+ Daemon Objects is designed to simplify using daemons in your ruby applications. Under the hood, it uses the
6
+ [daemons](http://daemons.rubyforge.org) gem, which is a mature and tested solution. But, it adds support for managing via rake tasks,
7
+ error handling and instrumentation.
8
+
9
+ The [daemons](http://daemons.rubyforge.org) gem also is intended to be used to daemonize a ruby script. DaemonObjects provides an
10
+ object-oriented framework for developing daemons. This allows the application developer to focus on the specific behavior of the daemon
11
+ instead of the infrastructure of daemon management.
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ gem 'daemon_objects'
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install daemon_objects
26
+
27
+ ## Usage
28
+
29
+ DaemonObjects will create daemons based on a simple convention. It will search a directory for files name \*Daemon.rb. These typically
30
+ will just inherit from the base Daemon class.
31
+
32
+ class MyDaemon < DaemonObjects::Base; end
33
+
34
+ This provides the basic daemon control methods (start, stop, run and restart) to your daemon.
35
+
36
+ To add behavior to your daemon, you will need a consumer. DaemonObjects will load the consumer using the name of the daemon and
37
+ will search in the same directory for it. For example, if your daemon is name MyDaemon, the consumer should be named MyConsumer.
38
+
39
+ A consumer needs to inherit from the consumer base and implement run. For example,
40
+
41
+ class MyConsumer < DaemonObjects::ConsumerBase
42
+
43
+ def run
44
+ loop do
45
+ "I'm looping"
46
+ sleep 5
47
+ end
48
+ end
49
+
50
+ end
51
+
52
+ ### Rake tasks
53
+
54
+ Once you have defined the daemon, you can control it with rake tasks. To access the rake tasks,
55
+ you will need to include the daemon\_objects railtie in config/application.rb.
56
+
57
+ require 'daemon_objects/railtie'
58
+
59
+ Rake tasks are created using the daemon file name. The rake syntax is:
60
+
61
+ rake daemon:<daemon_file_name>:<command>
62
+
63
+ For example, to start the MyDaemon daemon:
64
+
65
+ rake daemon:my_daemon:start
66
+
67
+ Four commands are supported
68
+
69
+ * start - Starts the daemon
70
+ * stop - Stops the daemon
71
+ * restart - Stops and then starts the daemon
72
+ * run - Runs the daemon synchronously
73
+
74
+ ### Amqp Support
75
+
76
+ _in beta_
77
+
78
+ DaemonObjects also has support for monitoring an amqp queue. This is done with the amqp gem. To support this
79
+ with your daemon, add `supports_amqp` to your daemon class.
80
+
81
+ class MyQueueProcessingDaemon < Daemon::Base
82
+ supports_amqp :endpoint => "http://localhost:5672",
83
+ :queue_name => "my_awesome_queue",
84
+ :worker_class => MyAmqpWorker
85
+ end
86
+
87
+ This will add the code to monitor the queue, so all you need now is code to handle the messages.
88
+
89
+ class MyQueueProcessingConsumer < Daemon::ConsumerBase
90
+
91
+ handle_messages_with do |payload|
92
+ puts "payload"
93
+ end
94
+
95
+ end
96
+
97
+ ### Logging
98
+
99
+ DaemonObjects will create a new log file for your daemon using the pattern _daemon\_file\_name_\_daemon.log. In a rails project,
100
+ this will be created in the log directory of your application.
101
+
102
+ ### Support for third-party libraries
103
+
104
+ DaemonObjects supports the following third-party libraries. If they are required in your application, your daemon will use them.
105
+
106
+ * [Airbrake](http://airbrake.io) - any errors that occur in the daemon will be reported to Airbrake.
107
+ * [NewRelic](http://newrelic.com) - amqp daemons will have instrument the handle\_message method and report to New Relic.
108
+
109
+ ## Contributing
110
+
111
+ 1. Fork it
112
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
113
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
114
+ 4. Push to the branch (`git push origin my-new-feature`)
115
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+
3
+
4
+ desc "Run rspec spec for those accustomed to the rake task"
5
+ task :spec do
6
+ sh 'rspec spec'
7
+ end
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'daemon_objects/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "daemon_objects"
8
+ spec.version = DaemonObjects::VERSION
9
+ spec.authors = ["Craig Israel"]
10
+ spec.email = ["craig@theisraels.net"]
11
+ spec.description = %q{ Object-oriented daemons}
12
+ spec.summary = %q{ Daemon objects provides an object-based interface to daemons}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "daemons", "~> 1.1"
22
+ spec.add_dependency "activesupport", "~> 3.2"
23
+ spec.add_dependency "amqp", "~> 0.9"
24
+ spec.add_dependency "rake"
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.3"
27
+ spec.add_development_dependency "rspec"
28
+ spec.add_development_dependency "pry-nav"
29
+ end
@@ -0,0 +1,34 @@
1
+ module DaemonObjects::AmqpSupport
2
+ attr_accessor :endpoint, :queue, :prefetch, :worker_class
3
+
4
+ def arguments
5
+ @arguments ||= {}
6
+ end
7
+
8
+ def run
9
+ logger.info "Preparing to start the AMQP watcher."
10
+
11
+ AMQP.start(endpoint) do |connection, open_ok|
12
+ logger.info "Starting up the AMQP watcher."
13
+
14
+ channel = AMQP::Channel.new(connection)
15
+ channel.prefetch(1) if prefetch
16
+
17
+ worker = worker_class.new(
18
+ channel,
19
+ get_consumer,
20
+ {
21
+ :queue_name => queue,
22
+ :arguments => arguments
23
+ })
24
+
25
+ worker.start
26
+
27
+ Signal.trap("INT") do
28
+ logger.info "Exiting process"
29
+ connection.close { EventMachine.stop }
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,75 @@
1
+ require 'daemon_objects/logging'
2
+
3
+ class DaemonObjects::Base
4
+ extend DaemonObjects::Logging
5
+
6
+ def self.consumes_amqp(opts={})
7
+ extend DaemonObjects::AmqpSupport
8
+ self.endpoint = opts.delete(:endpoint)
9
+ self.queue = opts.delete(:queue_name)
10
+ self.arguments["x-message-ttl"] = opts.delete(:ttl) if opts[:ttl]
11
+ self.prefetch = opts.delete(:prefetch)
12
+ self.worker_class = opts.delete(:worker_class)
13
+
14
+ logger.info "Configured to consume queue [#{queue}] at endpoint [#{endpoint}]"
15
+ end
16
+
17
+ def self.pid_directory
18
+ (defined? Rails) ? File.join(Rails.root, "tmp/pids") : "."
19
+ end
20
+
21
+ def self.consumer_class
22
+ @consumer_class ||= "#{self.to_s.gsub("Daemon", "")}Consumer".constantize
23
+ end
24
+
25
+ def self.proc_name
26
+ @proc_name ||= self.to_s.underscore
27
+ end
28
+
29
+ def self.get_consumer
30
+ consumer_class.new(logger)
31
+ end
32
+
33
+ def self.run
34
+ get_consumer.run
35
+ end
36
+
37
+ def self.after_fork
38
+ # daemonizing closes all file handles, so this will reopen the log
39
+ force_logger_reset
40
+ # this seems to be enough to initialize NewRelic if it's defined
41
+ defined?(NewRelic)
42
+ end
43
+
44
+ def self.start
45
+ # connection will get severed on fork, so disconnect first
46
+ ActiveRecord::Base.connection.disconnect! if defined?(ActiveRecord::Base)
47
+
48
+ FileUtils.mkdir_p(pid_directory)
49
+
50
+ Daemons.run_proc(proc_name,
51
+ { :ARGV => ["start", "-f"],
52
+ :log_dir => "/tmp",
53
+ :dir => pid_directory,
54
+ :log_output => true}) do
55
+
56
+ after_fork
57
+ run
58
+ end
59
+
60
+ rescue StandardError => e
61
+ logger.error(e.message)
62
+ logger.error(e.backtrace.join("\n"))
63
+ Airbrake.notify(e) if defined?(Airbrake)
64
+ end
65
+
66
+ def self.stop
67
+ Daemons.run_proc(proc_name, { :ARGV => [ "stop", "-f" ], :dir => pid_directory})
68
+ end
69
+
70
+ def self.restart
71
+ start
72
+ stop
73
+ end
74
+
75
+ end
@@ -0,0 +1,35 @@
1
+ class DaemonObjects::ConsumerBase
2
+
3
+ include ::NewRelic::Agent::Instrumentation::ControllerInstrumentation if defined?(::NewRelic)
4
+
5
+ class << self
6
+ attr_accessor :message_handler
7
+ end
8
+
9
+ attr_accessor :logger
10
+
11
+ def initialize(logger)
12
+ @logger = logger
13
+ end
14
+
15
+ def run
16
+ logger.info("Starting consumer")
17
+ end
18
+
19
+ def handle_message(payload)
20
+ logger.info("Handling message #{payload}")
21
+ handle_message_impl(payload)
22
+ logger.info("Completed handling message")
23
+ rescue StandardError => e
24
+ logger.error("#{e.class}: #{e.message}")
25
+ logger.error(e.backtrace.join("\n"))
26
+ Airbrake.notify(e) if defined?(Airbrake)
27
+ end
28
+
29
+ def self.handle_messages_with(&block)
30
+ raise StandardError.new("Provided block must take at least one argument - 'payload'") if block.arity < 1
31
+ define_method(:handle_message_impl, &block)
32
+ end
33
+
34
+ add_transaction_tracer :handle_message, :category => :task if defined?(::NewRelic)
35
+ end
@@ -0,0 +1,31 @@
1
+ module DaemonObjects
2
+ ROOT = File.join(File.dirname(__FILE__))
3
+
4
+ DAEMON_FILE_ENDING = "_daemon.rb"
5
+
6
+ class << self
7
+ attr_accessor :daemon_path
8
+
9
+ def daemon_path
10
+ @daemon_path ||= File.join(Rake.application.original_dir, "lib/daemons")
11
+ end
12
+
13
+ def daemons
14
+ @daemons ||= get_daemons
15
+ end
16
+
17
+ def get_daemon_name(path)
18
+ file = Pathname(path).basename.to_s
19
+ file.gsub!(/#{DAEMON_FILE_ENDING}$/, "")
20
+ end
21
+
22
+ private
23
+
24
+ def get_daemons
25
+ paths = Dir["#{daemon_path}/*#{DAEMON_FILE_ENDING}"]
26
+ warn "No daemons found at #{daemon_path}" if paths.empty?
27
+ paths.map{|p| get_daemon_name(p) }
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ module DaemonObjects::Logging
2
+
3
+ def log_filename
4
+ "#{to_s.underscore}.log"
5
+ end
6
+
7
+ def log_directory
8
+ (defined? Rails) ? File.join(Rails.root, "log") : "log"
9
+ end
10
+
11
+ def logger
12
+ @logger ||= create_logger
13
+ end
14
+
15
+ def create_logger
16
+ FileUtils.mkdir_p log_directory
17
+ logger = ::Logger.new(File.join(log_directory, log_filename))
18
+ logger.formatter = ::Logger::Formatter.new
19
+ logger
20
+ end
21
+
22
+ def logger=(value)
23
+ @logger = value
24
+ end
25
+
26
+ def force_logger_reset
27
+ @logger = nil
28
+ Rails.logger = logger if defined?(Rails)
29
+ end
30
+
31
+ end
@@ -0,0 +1,6 @@
1
+ class Railtie < Rails::Railtie
2
+ rake_tasks do
3
+ require "daemon_objects/loader"
4
+ load File.join(File.dirname(__FILE__), "tasks/daemon_objects.rake")
5
+ end
6
+ end if defined?(Rails)
@@ -0,0 +1,41 @@
1
+ namespace :daemon do
2
+
3
+ # create tasks for each daemon to start/stop/restart/run
4
+ DaemonObjects.daemons.each do |daemon|
5
+
6
+ namespace daemon do
7
+ description = "#{daemon.to_s.gsub("_", " ")} daemon"
8
+ desc "starts the #{description}; can substitute stop or restart for start, " +
9
+ "or use run to run the daemon in the foreground"
10
+
11
+ [:start, :stop, :run].each do |action|
12
+ task action => :environment do
13
+
14
+ require "daemon_objects"
15
+ require "#{DaemonObjects.daemon_path}/#{daemon}_daemon.rb"
16
+ require "#{DaemonObjects.daemon_path}/#{daemon}_consumer.rb"
17
+
18
+
19
+ puts "#{description} #{action}"
20
+ daemon_class = "#{daemon}_daemon".camelcase.constantize
21
+ daemon_class.send(action)
22
+ end
23
+ end
24
+
25
+ task :restart => [:environment, :stop, :start]
26
+ end
27
+ end
28
+
29
+ namespace :all do
30
+
31
+ desc 'start all daemons'
32
+ task :start => DaemonObjects.daemons.map{|d| "daemon:#{d}:start"}
33
+
34
+ desc 'stop all daemons'
35
+ task :stop => DaemonObjects.daemons.map{|d| "daemon:#{d}:stop"}
36
+
37
+ desc 'restart all daemons'
38
+ task :restart => [:stop, :start]
39
+ end
40
+
41
+ end
@@ -0,0 +1,3 @@
1
+ module DaemonObjects
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,14 @@
1
+ require "daemon_objects/version"
2
+ require 'active_support/core_ext/string'
3
+ require 'daemons'
4
+ require 'amqp'
5
+ require 'logger'
6
+
7
+ module DaemonObjects; end
8
+
9
+ require 'daemon_objects/loader'
10
+ require 'daemon_objects/base'
11
+ require 'daemon_objects/consumer_base'
12
+ require 'daemon_objects/amqp_support'
13
+ require 'daemon_objects/railtie'
14
+
@@ -0,0 +1,173 @@
1
+ require 'spec_helper'
2
+
3
+ describe DaemonObjects::Base do
4
+ after :each do
5
+ [ :MyDaemon, :ThreePartNameDaemon, :MyConsumer].each do |sym|
6
+ Object.send(:remove_const, sym) if Object.const_defined?(sym)
7
+ end
8
+ end
9
+
10
+ describe '#extends' do
11
+ it 'should extend logging' do
12
+ MyDaemon = Class.new(DaemonObjects::Base)
13
+ MyDaemon.singleton_class.included_modules.should include(DaemonObjects::Logging)
14
+ end
15
+ end
16
+
17
+ describe '#run' do
18
+ it 'should create new consumer' do
19
+ MyConsumer = Class.new(DaemonObjects::ConsumerBase)
20
+
21
+ MyDaemon = Class.new(DaemonObjects::Base) do
22
+ self.logger = StubLogger.new
23
+ end
24
+
25
+ MyDaemon.run
26
+ MyDaemon.logger.logged_output.should =~ /Starting consumer\n/
27
+ end
28
+
29
+ end
30
+
31
+ describe '#start' do
32
+ it 'should call daemon run_proc' do
33
+ MyDaemon = Class.new(DaemonObjects::Base)
34
+ Daemons.should_receive(:run_proc).
35
+ with(MyDaemon.proc_name,
36
+ { :ARGV => ['start', '-f'],
37
+ :log_dir => "/tmp",
38
+ :dir => MyDaemon.pid_directory,
39
+ :log_output => true
40
+ } )
41
+ MyDaemon.start
42
+ end
43
+ end
44
+
45
+ describe '#stop' do
46
+ it 'should call daemon stop_proc' do
47
+ MyDaemon = Class.new(DaemonObjects::Base)
48
+ Daemons.should_receive(:run_proc).
49
+ with(MyDaemon.proc_name,
50
+ { :ARGV => ['stop', '-f'],
51
+ :dir => MyDaemon.pid_directory})
52
+ MyDaemon.stop
53
+ end
54
+ end
55
+
56
+ describe '##consumer_class' do
57
+ it 'should constantize a file with multiple part name' do
58
+ ThreePartNameConsumer = Class.new
59
+ ThreePartNameDaemon = Class.new(DaemonObjects::Base)
60
+ ThreePartNameDaemon.consumer_class.should == ThreePartNameConsumer
61
+ end
62
+ end
63
+
64
+ describe '##proc_name' do
65
+ it 'should underscore class to get daemon name' do
66
+ ThreePartNameDaemon = Class.new(DaemonObjects::Base)
67
+ ThreePartNameDaemon.proc_name.should == "three_part_name_daemon"
68
+ end
69
+ end
70
+
71
+ describe 'AMQP support' do
72
+ let(:endpoint){ }
73
+
74
+ before :each do
75
+ MyWorker = Class.new do
76
+ def initialize(*args); end
77
+ def run; end
78
+ end
79
+ end
80
+
81
+ after :each do
82
+ Object.send( :remove_const, :MyWorker) if defined?(MyWorker)
83
+ end
84
+
85
+ it 'should start AMQP if daemon is an amqp consumer' do
86
+ MyConsumer = Class.new(DaemonObjects::ConsumerBase)
87
+ MyDaemon = Class.new(DaemonObjects::Base) do
88
+ consumes_amqp :endpoint => 'amqp://localhost:4567',
89
+ :queue_name => 'queue',
90
+ :worker_class => MyWorker
91
+ end
92
+
93
+ AMQP.should_receive(:start).with('amqp://localhost:4567').and_return(true)
94
+ MyDaemon.run
95
+ end
96
+
97
+ it 'should not start AMQP if daemon is not an amqp consumer' do
98
+ MyConsumer = Class.new(DaemonObjects::ConsumerBase)
99
+ MyDaemon = Class.new(DaemonObjects::Base)
100
+
101
+ AMQP.should_not_receive(:start)
102
+ MyDaemon.run
103
+
104
+ end
105
+
106
+ it 'should start a worker' do
107
+ MyConsumer = Class.new(DaemonObjects::ConsumerBase)
108
+ MyDaemon = Class.new(DaemonObjects::Base) do
109
+ consumes_amqp :endpoint => 'amqp://localhost:4567',
110
+ :queue_name => 'queue',
111
+ :worker_class => MyWorker
112
+ end
113
+
114
+ def AMQP.start(endpoint)
115
+ yield "connection", "open"
116
+ end
117
+
118
+ channel = double(AMQP::Channel)
119
+ AMQP::Channel.stub(:new).and_return(channel)
120
+ channel.should_not_receive(:prefetch)
121
+
122
+ worker = MyWorker.new
123
+ consumer = MyDaemon.get_consumer
124
+ MyDaemon.stub(:get_consumer).and_return(consumer)
125
+
126
+ MyWorker.should_receive(:new).
127
+ with(channel, consumer, {
128
+ :queue_name => 'queue',
129
+ :arguments => {}
130
+ }).
131
+ and_return(worker)
132
+ worker.should_receive(:start)
133
+
134
+ MyDaemon.run
135
+ end
136
+
137
+ it 'should use prefetch value when available' do
138
+ MyConsumer = Class.new(DaemonObjects::ConsumerBase)
139
+ MyDaemon = Class.new(DaemonObjects::Base) do
140
+ consumes_amqp :endpoint => 'amqp://localhost:4567',
141
+ :queue_name => 'queue',
142
+ :prefetch => 1,
143
+ :worker_class => MyWorker
144
+ end
145
+
146
+ def AMQP.start(endpoint)
147
+ yield "connection", "open"
148
+ end
149
+
150
+ channel = double(AMQP::Channel)
151
+ channel.should_receive(:prefetch).with(1)
152
+
153
+ AMQP::Channel.stub(:new).and_return(channel)
154
+
155
+ worker = MyWorker.new
156
+ consumer = MyDaemon.get_consumer
157
+ MyDaemon.stub(:get_consumer).and_return(consumer)
158
+
159
+ MyWorker.should_receive(:new).
160
+ with(channel, consumer, {
161
+ :queue_name => 'queue',
162
+ :arguments => {}
163
+ }).
164
+ and_return(worker)
165
+ worker.should_receive(:start)
166
+
167
+ MyDaemon.run
168
+ end
169
+ end
170
+
171
+ end
172
+
173
+
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe DaemonObjects::ConsumerBase do
4
+ describe '#handle_message' do
5
+ after :each do
6
+ Object.send(:remove_const, :Harness)
7
+ end
8
+
9
+ it 'should call the configured message handler' do
10
+
11
+ Harness = Class.new(DaemonObjects::ConsumerBase) do
12
+ def payloads_received
13
+ @payloads_received ||= []
14
+ end
15
+
16
+ handle_messages_with{|p| payloads_received << p }
17
+ end
18
+
19
+ h = Harness.new(StubLogger.new)
20
+ h.handle_message({:x => 1})
21
+
22
+ h.payloads_received.should == [{:x => 1}]
23
+ end
24
+ end
25
+
26
+ end
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+
3
+ describe DaemonObjects::Logging do
4
+ let(:harness) do
5
+ Class.new do
6
+ extend DaemonObjects::Logging
7
+ end
8
+ end
9
+
10
+ describe '#logger' do
11
+ it 'should create a logger at log/log_filename path' do
12
+ logger = StubLogger.new
13
+
14
+ Logger.stub(:new).
15
+ with("#{harness.log_directory}/#{harness.log_filename}").
16
+ and_return(logger)
17
+
18
+ harness.logger.info("starting consumer")
19
+
20
+ logger.logged_output.should =~ /starting consumer\n$/
21
+ end
22
+ end
23
+
24
+ describe '#create_logger' do
25
+ it 'should create a logger with timestamp formatting' do
26
+ logger = harness.logger
27
+ logger.formatter.class.should == ::Logger::Formatter
28
+ end
29
+ end
30
+
31
+ describe '#log_filename' do
32
+ before :each do
33
+ MyDaemon = Class.new do
34
+ extend DaemonObjects::Logging
35
+ end
36
+ end
37
+
38
+ after :each do
39
+ Object.send(:remove_const, :MyDaemon)
40
+ end
41
+
42
+ it 'should underscore name for log file' do
43
+ MyDaemon.log_filename.should == "my_daemon.log"
44
+ end
45
+ end
46
+
47
+ describe '#log_directory' do
48
+ it "should use 'log' for default log path" do
49
+ harness.log_directory.to_s.should == "log"
50
+ end
51
+
52
+ context 'with rails' do
53
+ before :each do
54
+ unless defined?(Rails)
55
+ module Rails
56
+ def self.root
57
+ "/root"
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ after :each do
64
+ Object.send(:remove_const, :Rails)
65
+ end
66
+
67
+ it 'should use Rails log path when Rails is defined' do
68
+ harness.log_directory.to_s.should == File.join(Rails.root, "log")
69
+ end
70
+ end
71
+
72
+ end
73
+
74
+
75
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe DaemonObjects do
4
+ describe '#daemons' do
5
+ it 'should get daemons from daemon_path' do
6
+ DaemonObjects.daemon_path = File.join(FIXTURES_PATH, "daemon_path_spec")
7
+ DaemonObjects.daemons.sort.should == ["daemon_one", "daemon_two"]
8
+ end
9
+ end
10
+
11
+ describe '#get_daemon_name' do
12
+ it 'should parse out path and Daemon.rb' do
13
+ path = File.join(FIXTURES_PATH, "daemon_path_spec/daemon_one_daemon.rb")
14
+ DaemonObjects.get_daemon_name(path).should == "daemon_one"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,25 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ require 'pry'
8
+ SPEC_PATH = File.dirname(__FILE__)
9
+ Dir[File.join(SPEC_PATH, "support/**/*.rb")].each{|f| require f}
10
+
11
+ FIXTURES_PATH = File.join(SPEC_PATH, "fixtures")
12
+
13
+ RSpec.configure do |config|
14
+ config.treat_symbols_as_metadata_keys_with_true_values = true
15
+ config.run_all_when_everything_filtered = true
16
+ config.filter_run :focus
17
+
18
+ # Run specs in random order to surface order dependencies. If you find an
19
+ # order dependency and want to debug it, you can fix the order by providing
20
+ # the seed, which is printed after each run.
21
+ # --seed 1234
22
+ config.order = 'random'
23
+ end
24
+
25
+ require File.join(File.dirname(__FILE__), "../lib/daemon_objects.rb")
@@ -0,0 +1,23 @@
1
+ class StubLogger
2
+
3
+ attr_accessor :logger
4
+
5
+ def method_missing(sym, *args, &block)
6
+ logger.send(sym, *args, &block)
7
+ end
8
+
9
+ def initialize
10
+ @io = StringIO.new
11
+
12
+ require 'logger'
13
+ @logger = Logger.new(@io)
14
+ end
15
+
16
+ def logged_output
17
+ @io.string
18
+ end
19
+
20
+ def messages
21
+ logged_output.split("\n")
22
+ end
23
+ end
metadata ADDED
@@ -0,0 +1,191 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: daemon_objects
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Craig Israel
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-09-05 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: daemons
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.1'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.1'
30
+ - !ruby/object:Gem::Dependency
31
+ name: activesupport
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '3.2'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '3.2'
46
+ - !ruby/object:Gem::Dependency
47
+ name: amqp
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '0.9'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '0.9'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rake
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: bundler
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: '1.3'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: '1.3'
94
+ - !ruby/object:Gem::Dependency
95
+ name: rspec
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: pry-nav
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ description: ! ' Object-oriented daemons'
127
+ email:
128
+ - craig@theisraels.net
129
+ executables: []
130
+ extensions: []
131
+ extra_rdoc_files: []
132
+ files:
133
+ - .gitignore
134
+ - .rspec
135
+ - .travis.yml
136
+ - Gemfile
137
+ - LICENSE.txt
138
+ - README.md
139
+ - Rakefile
140
+ - daemon_objects.gemspec
141
+ - lib/daemon_objects.rb
142
+ - lib/daemon_objects/amqp_support.rb
143
+ - lib/daemon_objects/base.rb
144
+ - lib/daemon_objects/consumer_base.rb
145
+ - lib/daemon_objects/loader.rb
146
+ - lib/daemon_objects/logging.rb
147
+ - lib/daemon_objects/railtie.rb
148
+ - lib/daemon_objects/tasks/daemon_objects.rake
149
+ - lib/daemon_objects/version.rb
150
+ - spec/fixtures/daemon_path_spec/daemon_one_daemon.rb
151
+ - spec/fixtures/daemon_path_spec/daemon_two_daemon.rb
152
+ - spec/lib/daemon_objects/base_spec.rb
153
+ - spec/lib/daemon_objects/consumer_base_spec.rb
154
+ - spec/lib/daemon_objects/logging_spec.rb
155
+ - spec/lib/daemon_objects_spec.rb
156
+ - spec/spec_helper.rb
157
+ - spec/support/stub_logger.rb
158
+ homepage: ''
159
+ licenses:
160
+ - MIT
161
+ post_install_message:
162
+ rdoc_options: []
163
+ require_paths:
164
+ - lib
165
+ required_ruby_version: !ruby/object:Gem::Requirement
166
+ none: false
167
+ requirements:
168
+ - - ! '>='
169
+ - !ruby/object:Gem::Version
170
+ version: '0'
171
+ required_rubygems_version: !ruby/object:Gem::Requirement
172
+ none: false
173
+ requirements:
174
+ - - ! '>='
175
+ - !ruby/object:Gem::Version
176
+ version: '0'
177
+ requirements: []
178
+ rubyforge_project:
179
+ rubygems_version: 1.8.23
180
+ signing_key:
181
+ specification_version: 3
182
+ summary: Daemon objects provides an object-based interface to daemons
183
+ test_files:
184
+ - spec/fixtures/daemon_path_spec/daemon_one_daemon.rb
185
+ - spec/fixtures/daemon_path_spec/daemon_two_daemon.rb
186
+ - spec/lib/daemon_objects/base_spec.rb
187
+ - spec/lib/daemon_objects/consumer_base_spec.rb
188
+ - spec/lib/daemon_objects/logging_spec.rb
189
+ - spec/lib/daemon_objects_spec.rb
190
+ - spec/spec_helper.rb
191
+ - spec/support/stub_logger.rb