daemon_objects 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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