famoseagle-sweat_shop 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/History.txt +6 -0
- data/LICENSE +20 -0
- data/README.markdown +65 -0
- data/Rakefile +41 -0
- data/VERSION.yml +4 -0
- data/config/defaults.yml +4 -0
- data/config/sweatshop.yml +12 -0
- data/lib/kestrel.rb +29 -0
- data/lib/sweat_shop.rb +107 -0
- data/lib/sweat_shop/metaid.rb +5 -0
- data/lib/sweat_shop/sweatd.rb +67 -0
- data/lib/sweat_shop/worker.rb +130 -0
- data/test/hello_worker.rb +16 -0
- data/test/test_functional_worker.rb +35 -0
- data/test/test_helper.rb +14 -0
- data/test/test_sweatshop.rb +67 -0
- metadata +70 -0
data/History.txt
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2009 Amos Elliston
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# SweatShop
|
|
2
|
+
|
|
3
|
+
SweatShop provides an api to background resource intensive tasks. Much of the api design was copied from Workling, with a few tweaks.
|
|
4
|
+
Currently, it runs kestrel, but it can support any number of queues.
|
|
5
|
+
|
|
6
|
+
## Installing
|
|
7
|
+
|
|
8
|
+
gem install sweat_shop
|
|
9
|
+
freeze in your rails directory
|
|
10
|
+
cd vendor/gems/sweat_shop
|
|
11
|
+
rake setup
|
|
12
|
+
|
|
13
|
+
## Writing workers
|
|
14
|
+
|
|
15
|
+
Put `email_worker.rb` into app/workers and sublcass `SweatShop::Worker`:
|
|
16
|
+
|
|
17
|
+
class EmailWorker
|
|
18
|
+
def send_mail(to)
|
|
19
|
+
user = User.find_by_id(to)
|
|
20
|
+
Mailer.deliver_welcome(to)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
Then, anywhere in your app you can execute:
|
|
25
|
+
|
|
26
|
+
EmailWorker.async_send_mail(1)
|
|
27
|
+
|
|
28
|
+
The `async` signifies that this task will be placed on a queue to be serviced by the EmailWorker possibly on another machine. You can also
|
|
29
|
+
call:
|
|
30
|
+
|
|
31
|
+
EmailWorker.send_mail(1)
|
|
32
|
+
|
|
33
|
+
That will do the work immediately, without placing the task on the queue. You can also define a `queue_group` at the top of the file
|
|
34
|
+
which will allow you to split workers out into logical groups. This is important if you have various machines serving different
|
|
35
|
+
queues.
|
|
36
|
+
|
|
37
|
+
## Running the queue
|
|
38
|
+
|
|
39
|
+
SweatShop has been tested with Kestrel, but it will also work with Starling. You can install and start kestrel following the instructions here:
|
|
40
|
+
|
|
41
|
+
http://github.com/robey/kestrel/tree/master
|
|
42
|
+
|
|
43
|
+
config/sweatshop.yml specifies the machine address of the queue (default localhost:22133).
|
|
44
|
+
|
|
45
|
+
## Running the workers
|
|
46
|
+
|
|
47
|
+
Assuming you ran `rake setup` in Rails, you can type:
|
|
48
|
+
|
|
49
|
+
script/sweatshop
|
|
50
|
+
|
|
51
|
+
By default, the script will run all workers defined in the app/workers dir. Every task will be processed on each queue using a round-robin algorithm. You can also add the `-d` flag which will put the worker in daemon mode. The daemon also takes other params. Add a `-h` for more details.
|
|
52
|
+
|
|
53
|
+
script/sweatshop -d
|
|
54
|
+
script/sweatshop -d stop
|
|
55
|
+
|
|
56
|
+
If you would like to run SweatShop as a daemon on a linux machine, use the initd.sh script provided in the sweat_shop/script dir.
|
|
57
|
+
|
|
58
|
+
# REQUIREMENTS
|
|
59
|
+
|
|
60
|
+
i_can_daemonize
|
|
61
|
+
memcache (for kestrel)
|
|
62
|
+
|
|
63
|
+
# LICENSE
|
|
64
|
+
|
|
65
|
+
Copyright (c) 2009 Amos Elliston, Geni.com; Published under The MIT License, see License
|
data/Rakefile
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require 'rake'
|
|
2
|
+
require 'rake/testtask'
|
|
3
|
+
require 'rake/rdoctask'
|
|
4
|
+
require 'rcov/rcovtask'
|
|
5
|
+
|
|
6
|
+
begin
|
|
7
|
+
require 'jeweler'
|
|
8
|
+
Jeweler::Tasks.new do |s|
|
|
9
|
+
s.name = "sweat_shop"
|
|
10
|
+
s.summary = %Q{SweatShop is a simple asynchronous worker queue build on top of rabbitmq/ampq}
|
|
11
|
+
s.email = "amos@geni.com"
|
|
12
|
+
s.homepage = "http://github.com/famoseagle/sweat-shop"
|
|
13
|
+
s.description = "TODO"
|
|
14
|
+
s.authors = ["Amos Elliston"]
|
|
15
|
+
s.files = FileList["[A-Z]*", "{lib,test,config}/**/*"]
|
|
16
|
+
end
|
|
17
|
+
rescue LoadError
|
|
18
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
Rake::TestTask.new
|
|
22
|
+
|
|
23
|
+
Rake::RDocTask.new do |rdoc|
|
|
24
|
+
rdoc.rdoc_dir = 'rdoc'
|
|
25
|
+
rdoc.title = 'new_project'
|
|
26
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
|
27
|
+
rdoc.rdoc_files.include('README*')
|
|
28
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
Rcov::RcovTask.new do |t|
|
|
32
|
+
t.libs << 'test'
|
|
33
|
+
t.test_files = FileList['test/**/*_test.rb']
|
|
34
|
+
t.verbose = true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
task :default => :test
|
|
38
|
+
|
|
39
|
+
task :setup do
|
|
40
|
+
require File.dirname(__FILE__) + '/install'
|
|
41
|
+
end
|
data/VERSION.yml
ADDED
data/config/defaults.yml
ADDED
data/lib/kestrel.rb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
class Kestrel
|
|
2
|
+
attr_reader :client, :servers
|
|
3
|
+
|
|
4
|
+
def initialize(opts)
|
|
5
|
+
@servers = opts[:servers]
|
|
6
|
+
@client = opts[:client] || MemCache.new(@servers)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def queue_size(queue)
|
|
10
|
+
size = 0
|
|
11
|
+
stats = client.stats
|
|
12
|
+
servers.each do |server|
|
|
13
|
+
size += stats[server]["queue_#{queue}_items"].to_i
|
|
14
|
+
end
|
|
15
|
+
size
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def enqueue(queue, data)
|
|
19
|
+
client.set(queue, data)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def dequeue(queue)
|
|
23
|
+
client.get("#{queue}/open")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def confirm(queue)
|
|
27
|
+
client.get("#{queue}/close")
|
|
28
|
+
end
|
|
29
|
+
end
|
data/lib/sweat_shop.rb
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'digest'
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
$:.unshift(File.dirname(__FILE__))
|
|
6
|
+
require 'kestrel'
|
|
7
|
+
require 'sweat_shop/worker'
|
|
8
|
+
|
|
9
|
+
module SweatShop
|
|
10
|
+
extend self
|
|
11
|
+
|
|
12
|
+
def workers
|
|
13
|
+
@workers ||= []
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def workers=(workers)
|
|
17
|
+
@workers = workers
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def workers_in_group(groups)
|
|
21
|
+
groups = [groups] unless groups.is_a?(Array)
|
|
22
|
+
if groups.include?(:all)
|
|
23
|
+
workers
|
|
24
|
+
else
|
|
25
|
+
workers.select do |worker|
|
|
26
|
+
groups.include?(worker.queue_group)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def do_tasks(workers)
|
|
32
|
+
loop do
|
|
33
|
+
wait = true
|
|
34
|
+
workers.each do |worker|
|
|
35
|
+
if task = worker.dequeue
|
|
36
|
+
worker.do_task(task)
|
|
37
|
+
wait = false
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
exit if stop?
|
|
41
|
+
sleep 1 if wait
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def do_all_tasks
|
|
46
|
+
do_tasks(
|
|
47
|
+
workers_in_group(:all)
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def do_default_tasks
|
|
52
|
+
do_tasks(
|
|
53
|
+
workers_in_group(:default)
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def stop
|
|
58
|
+
@stop = true
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def stop?
|
|
62
|
+
@stop
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def config
|
|
66
|
+
@config ||= begin
|
|
67
|
+
defaults = YAML.load_file(File.dirname(__FILE__) + '/../config/defaults.yml')
|
|
68
|
+
if defined?(RAILS_ROOT)
|
|
69
|
+
file = RAILS_ROOT + '/config/sweatshop.yml'
|
|
70
|
+
if File.exist?(file)
|
|
71
|
+
YAML.load_file(file)[RAILS_ENV || 'development']
|
|
72
|
+
else
|
|
73
|
+
defaults['enable'] = false
|
|
74
|
+
defaults
|
|
75
|
+
end
|
|
76
|
+
else
|
|
77
|
+
defaults
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def queue_sizes
|
|
83
|
+
workers.inject([]) do |all, worker|
|
|
84
|
+
all << [worker, worker.queue_size]
|
|
85
|
+
all
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def pp_sizes
|
|
90
|
+
max_width = workers.collect{|w| w.to_s.size}.max
|
|
91
|
+
puts '-' * (max_width + 10)
|
|
92
|
+
puts queue_sizes.collect{|p| sprintf("%-#{max_width}s %2s", p.first, p.last)}.join("\n")
|
|
93
|
+
puts '-' * (max_width + 10)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def queue
|
|
97
|
+
@queue ||= Kestrel.new(:servers => config['servers'])
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def queue=(queue)
|
|
101
|
+
@queue = queue
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
if defined?(RAILS_ROOT)
|
|
106
|
+
Dir.glob(RAILS_ROOT + '/app/workers/*.rb').each{|worker| require worker }
|
|
107
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/../sweat_shop'
|
|
2
|
+
require 'i_can_daemonize'
|
|
3
|
+
|
|
4
|
+
module SweatShop
|
|
5
|
+
class Sweatd
|
|
6
|
+
include ICanDaemonize
|
|
7
|
+
queues = []
|
|
8
|
+
groups = []
|
|
9
|
+
rails_root = nil
|
|
10
|
+
|
|
11
|
+
arg '--workers=Worker,Worker', 'Workers to service (Default is all)' do |value|
|
|
12
|
+
queues = value.split(',')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
arg '--groups=GROUP,GROUP', 'Groups of queues to service' do |value|
|
|
16
|
+
groups = value.split(',').collect{|g| g.to_sym}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
arg '--worker-file=WORKERFILE', 'Worker file to load' do |value|
|
|
20
|
+
require value
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
arg '--worker-dir=WORKERDIR', 'Directory containing workers' do |value|
|
|
24
|
+
Dir.glob(value + '*.rb').each{|worker| require worker}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
arg '--rails=DIR', 'Pass in RAILS_ROOT to run this daemon in a rails environment' do |value|
|
|
28
|
+
rails_root = value
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
sig(:term, :int) do
|
|
32
|
+
puts "Shutting down sweatd..."
|
|
33
|
+
SweatShop.stop
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
before do
|
|
37
|
+
if rails_root
|
|
38
|
+
puts "Loading Rails..."
|
|
39
|
+
require rails_root + '/config/environment'
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
daemonize(:kill_timeout => 20) do
|
|
44
|
+
workers = []
|
|
45
|
+
|
|
46
|
+
if groups.any?
|
|
47
|
+
workers += SweatShop.workers_in_group(groups)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
if queues.any?
|
|
51
|
+
workers += queues.collect{|q| Object.module_eval(q)}
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
if workers.any?
|
|
55
|
+
worker_str = workers.join(',')
|
|
56
|
+
puts "Starting #{worker_str}..."
|
|
57
|
+
$0 = "Sweatd: #{worker_str}"
|
|
58
|
+
SweatShop.do_tasks(workers)
|
|
59
|
+
else
|
|
60
|
+
puts "Starting all workers..."
|
|
61
|
+
$0 = 'Sweatd: all'
|
|
62
|
+
SweatShop.do_all_tasks
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/metaid'
|
|
2
|
+
|
|
3
|
+
module SweatShop
|
|
4
|
+
class Worker
|
|
5
|
+
@@logger = nil
|
|
6
|
+
|
|
7
|
+
def self.inherited(subclass)
|
|
8
|
+
self.workers << subclass
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.method_missing(method, *args, &block)
|
|
12
|
+
if method.to_s =~ /^async_(.*)/ and config['enable']
|
|
13
|
+
method = $1
|
|
14
|
+
expected_args = instance.method(method).arity
|
|
15
|
+
if expected_args != args.size
|
|
16
|
+
raise ArgumentError.new("#{method} expects #{expected_args} arguments")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
uid = ::Digest::MD5.hexdigest("#{name}:#{method}:#{args}:#{Time.now.to_f}")
|
|
20
|
+
task = {:args => args, :method => method, :uid => uid, :queued_at => Time.now.to_i}
|
|
21
|
+
|
|
22
|
+
log("Putting #{uid} on #{queue_name}")
|
|
23
|
+
enqueue(task)
|
|
24
|
+
|
|
25
|
+
uid
|
|
26
|
+
elsif instance.respond_to?(method)
|
|
27
|
+
instance.send(method, *args)
|
|
28
|
+
else
|
|
29
|
+
super
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.instance
|
|
34
|
+
@instance ||= new
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.config
|
|
38
|
+
SweatShop.config
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.queue_name
|
|
42
|
+
@queue_name ||= self.to_s
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.queue_size
|
|
46
|
+
queue.queue_size(queue_name)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.enqueue(task)
|
|
50
|
+
queue.enqueue(queue_name, task)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.dequeue
|
|
54
|
+
queue.dequeue(queue_name)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.confirm
|
|
58
|
+
queue.confirm(queue_name)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def self.do_tasks
|
|
62
|
+
while task = dequeue
|
|
63
|
+
do_task(task)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def self.do_task(task)
|
|
68
|
+
call_before_task(task)
|
|
69
|
+
|
|
70
|
+
queued_at = task[:queued_at] ? "(queued #{Time.at(task[:queued_at]).strftime('%Y/%m/%d %H:%M:%S')})" : ''
|
|
71
|
+
log("Dequeuing #{queue_name}::#{task[:method]} #{queued_at}")
|
|
72
|
+
task[:result] = instance.send(task[:method], *task[:args])
|
|
73
|
+
|
|
74
|
+
call_after_task(task)
|
|
75
|
+
confirm
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def self.call_before_task(task)
|
|
79
|
+
superclass.call_before_task(task) if superclass.respond_to?(:call_before_task)
|
|
80
|
+
before_task.call(task) if before_task
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def self.call_after_task(task)
|
|
84
|
+
superclass.call_after_task(task) if superclass.respond_to?(:call_after_task)
|
|
85
|
+
after_task.call(task) if after_task
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def self.workers
|
|
89
|
+
SweatShop.workers
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def self.log(msg)
|
|
93
|
+
return if logger == :silent
|
|
94
|
+
logger ? logger.debug(msg) : puts(msg)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def self.logger
|
|
98
|
+
@@logger
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def self.logger=(logger)
|
|
102
|
+
@@logger = logger
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def self.before_task(&block)
|
|
106
|
+
if block
|
|
107
|
+
@before_task = block
|
|
108
|
+
else
|
|
109
|
+
@before_task
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def self.after_task(&block)
|
|
114
|
+
if block
|
|
115
|
+
@after_task = block
|
|
116
|
+
else
|
|
117
|
+
@after_task
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def self.queue
|
|
122
|
+
SweatShop.queue
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def self.queue_group(group=nil)
|
|
126
|
+
group ? meta_def(:_queue_group){ group } : _queue_group
|
|
127
|
+
end
|
|
128
|
+
queue_group :default
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# hack for functional tests
|
|
2
|
+
require File.dirname(__FILE__) + '/../../../memcache/lib/memcache_extended'
|
|
3
|
+
require File.dirname(__FILE__) + '/../../../memcache/lib/memcache_util'
|
|
4
|
+
|
|
5
|
+
class HelloWorker < SweatShop::Worker
|
|
6
|
+
TEST_FILE = File.dirname(__FILE__) + '/test.txt' unless defined?(TEST_FILE)
|
|
7
|
+
|
|
8
|
+
def hello(name)
|
|
9
|
+
puts name
|
|
10
|
+
"Hi, #{name}"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
after_task do |task|
|
|
14
|
+
File.open(TEST_FILE, 'w'){|f| f << task[:result]}
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/../lib/sweat_shop'
|
|
2
|
+
require File.dirname(__FILE__) + '/test_helper'
|
|
3
|
+
require File.dirname(__FILE__) + '/hello_worker'
|
|
4
|
+
|
|
5
|
+
class WorkerTest < Test::Unit::TestCase
|
|
6
|
+
|
|
7
|
+
def setup
|
|
8
|
+
File.delete(HelloWorker::TEST_FILE) if File.exist?(HelloWorker::TEST_FILE)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def teardown
|
|
12
|
+
File.delete(HelloWorker::TEST_FILE) if File.exist?(HelloWorker::TEST_FILE)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# remove 'x' and start kestrel to run
|
|
16
|
+
test "daemon" do
|
|
17
|
+
begin
|
|
18
|
+
SweatShop.queue = nil
|
|
19
|
+
SweatShop::Worker.logger = :silent
|
|
20
|
+
|
|
21
|
+
worker = File.expand_path(File.dirname(__FILE__) + '/hello_worker')
|
|
22
|
+
sweatd = "#{File.dirname(__FILE__)}/../lib/sweat_shop/sweatd.rb"
|
|
23
|
+
uid = HelloWorker.async_hello('Amos')
|
|
24
|
+
|
|
25
|
+
`ruby #{sweatd} --worker-file #{worker} start`
|
|
26
|
+
`ruby #{sweatd} stop`
|
|
27
|
+
|
|
28
|
+
File.delete('sweatd.log') if File.exist?('sweatd.log')
|
|
29
|
+
assert_equal 'Hi, Amos', File.read(HelloWorker::TEST_FILE)
|
|
30
|
+
rescue MemCache::MemCacheError => e
|
|
31
|
+
puts "\n\n*** Start kestrel on localhost to run all functional tests. ***\n\n"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
end
|
data/test/test_helper.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require 'test/unit'
|
|
2
|
+
|
|
3
|
+
class << Test::Unit::TestCase
|
|
4
|
+
def test(name, &block)
|
|
5
|
+
test_name = "test_#{name.gsub(/[\s\W]/,'_')}"
|
|
6
|
+
raise ArgumentError, "#{test_name} is already defined" if self.instance_methods.include? test_name
|
|
7
|
+
define_method test_name, &block
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def xtest(name, &block)
|
|
11
|
+
# no-op, an empty test method is defined to prevent "no tests in testcase" errors when all tests are disabled
|
|
12
|
+
define_method(:test_disabled) { assert true }
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/../../../memcache/lib/memcache_mock'
|
|
2
|
+
require File.dirname(__FILE__) + '/test_helper'
|
|
3
|
+
require File.dirname(__FILE__) + '/../lib/sweat_shop'
|
|
4
|
+
|
|
5
|
+
class SweatShopTest < Test::Unit::TestCase
|
|
6
|
+
SweatShop.workers = []
|
|
7
|
+
SweatShop.queue = Kestrel.new(:client => MemCacheMock.new)
|
|
8
|
+
|
|
9
|
+
class HelloWorker < SweatShop::Worker
|
|
10
|
+
def hello(name)
|
|
11
|
+
"Hi, #{name}"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class GroupedWorker < SweatShop::Worker
|
|
16
|
+
queue_group :foo
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
test "group workers" do
|
|
20
|
+
assert_equal [HelloWorker, GroupedWorker], SweatShop.workers_in_group(:all)
|
|
21
|
+
assert_equal [HelloWorker], SweatShop.workers_in_group(:default)
|
|
22
|
+
assert_equal [GroupedWorker], SweatShop.workers_in_group(:foo)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
test "synch call" do
|
|
26
|
+
worker = HelloWorker.new
|
|
27
|
+
assert_equal "Hi, Amos", worker.hello('Amos')
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
test "uid" do
|
|
31
|
+
SweatShop::Worker.logger = :silent
|
|
32
|
+
uid = HelloWorker.async_hello('Amos')
|
|
33
|
+
assert_not_nil uid
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
test "before task" do
|
|
37
|
+
HelloWorker.before_task do
|
|
38
|
+
"hello"
|
|
39
|
+
end
|
|
40
|
+
assert_equal "hello", HelloWorker.before_task.call
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
test "after task" do
|
|
44
|
+
HelloWorker.after_task do
|
|
45
|
+
"goodbye"
|
|
46
|
+
end
|
|
47
|
+
assert_equal "goodbye", HelloWorker.after_task.call
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
test "chainable before tasks" do
|
|
51
|
+
MESSAGES = []
|
|
52
|
+
class BaseWorker < SweatShop::Worker
|
|
53
|
+
before_task do |task|
|
|
54
|
+
MESSAGES << 'base'
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
class SubWorker < BaseWorker
|
|
58
|
+
before_task do |task|
|
|
59
|
+
MESSAGES << 'sub'
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
SubWorker.call_before_task('foo')
|
|
63
|
+
assert_equal ['base', 'sub'], MESSAGES
|
|
64
|
+
SweatShop.workers.delete(BaseWorker)
|
|
65
|
+
SweatShop.workers.delete(SubWorker)
|
|
66
|
+
end
|
|
67
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: famoseagle-sweat_shop
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.3.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Amos Elliston
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
|
|
12
|
+
date: 2009-03-18 00:00:00 -07:00
|
|
13
|
+
default_executable:
|
|
14
|
+
dependencies: []
|
|
15
|
+
|
|
16
|
+
description: TODO
|
|
17
|
+
email: amos@geni.com
|
|
18
|
+
executables: []
|
|
19
|
+
|
|
20
|
+
extensions: []
|
|
21
|
+
|
|
22
|
+
extra_rdoc_files: []
|
|
23
|
+
|
|
24
|
+
files:
|
|
25
|
+
- History.txt
|
|
26
|
+
- LICENSE
|
|
27
|
+
- Rakefile
|
|
28
|
+
- README.markdown
|
|
29
|
+
- VERSION.yml
|
|
30
|
+
- lib/kestrel.rb
|
|
31
|
+
- lib/sweat_shop
|
|
32
|
+
- lib/sweat_shop/metaid.rb
|
|
33
|
+
- lib/sweat_shop/sweatd.rb
|
|
34
|
+
- lib/sweat_shop/worker.rb
|
|
35
|
+
- lib/sweat_shop.rb
|
|
36
|
+
- test/hello_worker.rb
|
|
37
|
+
- test/test_functional_worker.rb
|
|
38
|
+
- test/test_helper.rb
|
|
39
|
+
- test/test_sweatshop.rb
|
|
40
|
+
- config/defaults.yml
|
|
41
|
+
- config/sweatshop.yml
|
|
42
|
+
has_rdoc: true
|
|
43
|
+
homepage: http://github.com/famoseagle/sweat-shop
|
|
44
|
+
post_install_message:
|
|
45
|
+
rdoc_options:
|
|
46
|
+
- --inline-source
|
|
47
|
+
- --charset=UTF-8
|
|
48
|
+
require_paths:
|
|
49
|
+
- lib
|
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: "0"
|
|
55
|
+
version:
|
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: "0"
|
|
61
|
+
version:
|
|
62
|
+
requirements: []
|
|
63
|
+
|
|
64
|
+
rubyforge_project:
|
|
65
|
+
rubygems_version: 1.2.0
|
|
66
|
+
signing_key:
|
|
67
|
+
specification_version: 2
|
|
68
|
+
summary: SweatShop is a simple asynchronous worker queue build on top of rabbitmq/ampq
|
|
69
|
+
test_files: []
|
|
70
|
+
|