stormtroopers 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +37 -3
- data/lib/stormtroopers/army.rb +26 -20
- data/lib/stormtroopers/trooper.rb +4 -6
- data/lib/stormtroopers/trooper/delayed_job.rb +7 -6
- data/lib/stormtroopers/version.rb +1 -1
- data/spec/stormtroopers/army_spec.rb +122 -0
- data/spec/stormtroopers/factory/delayed_job_spec.rb +42 -0
- data/spec/stormtroopers/factory_spec.rb +28 -0
- data/spec/stormtroopers/trooper/delayed_job_spec.rb +56 -0
- data/spec/stormtroopers/trooper_spec.rb +56 -0
- metadata +12 -2
data/README.md
CHANGED
@@ -1,6 +1,10 @@
|
|
1
|
-
# Stormtroopers
|
1
|
+
# Stormtroopers
|
2
2
|
|
3
|
-
Stormtroopers is a
|
3
|
+
Stormtroopers is a JRuby execution environment for background tasks using threads. It can use different backends, currently it comes with a DelayedJob (light) backend only. **NOTE: While the basic functionality works and is under test, Stormtroopers is still a Work In Progress!**
|
4
|
+
|
5
|
+
## Prerequisites
|
6
|
+
|
7
|
+
JRuby 1.7.0+ in 1.9 mode. Your code and the libraries you are using must be threadsafe. If you're running with Rails in development mode be sure that you do not run more than 1 thread at a time!
|
4
8
|
|
5
9
|
## Installation
|
6
10
|
|
@@ -18,7 +22,37 @@ Or install it yourself as:
|
|
18
22
|
|
19
23
|
## Usage
|
20
24
|
|
21
|
-
|
25
|
+
Stormtroopers are organized in Armies, each Army has a Factory that produces Troopers. Each Trooper runs a job inside it's own Thread (which is disposed of after job completion or failure).
|
26
|
+
|
27
|
+
Configure Stormtroopers using a config file 'config/stormtroopers.yml' and execute it using 'bundle exec stormtroopers' from the root directory of your application, here's an example that specifies workers for different DelayedJob queues:
|
28
|
+
|
29
|
+
```yaml
|
30
|
+
armies:
|
31
|
+
- factory:
|
32
|
+
type: :delayed_job
|
33
|
+
queues:
|
34
|
+
- emails
|
35
|
+
- posts
|
36
|
+
max_threads: 10
|
37
|
+
name: "Comms"
|
38
|
+
- factory:
|
39
|
+
type: :delayed_job
|
40
|
+
queues:
|
41
|
+
- calculation
|
42
|
+
- graphs
|
43
|
+
max_threads: 5
|
44
|
+
name: "Math"
|
45
|
+
```
|
46
|
+
|
47
|
+
The above configuration specifies two armies, each with a DelayedJob factory to produce its Troopers.
|
48
|
+
|
49
|
+
Each Army can be given a set of options:
|
50
|
+
|
51
|
+
- factory: options that are passed on to the Army's Trooper producing Factory
|
52
|
+
- max_threads: the maximum number of Troopers for this Army that may be concurrently running a job at any time
|
53
|
+
- name: the Army's name, this is used when logging
|
54
|
+
|
55
|
+
You set the Army's Factory with the factory type or class option. When using type this is translated into a builtin class, when using class you can specify the name of whatever class you like (it needs to implement the appropriate interface to be able to work of course, have a look through the source code and specs to figure out what you need to do). Besides specifying the type or class, you can also specify the Factory's name (used for logging purposes), if you don't give the Factory a name then the Army's name is propagated to the Factory. The factory options also include specific settings for the chosen backend, for DelayedJob you can specify the queues that jobs may be picked up from. If you don't specify queues then the factory will pick up jobs from all queues.
|
22
56
|
|
23
57
|
## Contributing
|
24
58
|
|
data/lib/stormtroopers/army.rb
CHANGED
@@ -4,33 +4,32 @@ module Stormtroopers
|
|
4
4
|
|
5
5
|
def initialize(config)
|
6
6
|
@name = config[:name] || factory_class(config).name
|
7
|
-
@factory = factory_class(config).new(config[:factory])
|
7
|
+
@factory = factory_class(config).new({name: config[:name]}.merge(config[:factory]))
|
8
8
|
@max_threads = config[:max_threads] || 1
|
9
9
|
@threads = []
|
10
10
|
end
|
11
11
|
|
12
12
|
def factory_class(config)
|
13
|
-
@factory_class ||=
|
14
|
-
raise ArgumentError, "Factory class or type must be defined" if config[:factory][:class].blank? && config[:factory][:type].blank?
|
15
|
-
class_name ||= config[:factory].delete(:class)
|
16
|
-
class_name ||= "stormtroopers/#{config[:factory].delete(:type)}_factory".camelize
|
17
|
-
class_name.constantize
|
18
|
-
end
|
13
|
+
@factory_class ||= self.class.factory_class(config)
|
19
14
|
end
|
20
15
|
|
21
16
|
def manage
|
22
17
|
cleanup
|
23
18
|
if threads.count < max_threads
|
24
19
|
if trooper = factory.produce
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
20
|
+
run_trooper(trooper)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def run_trooper(trooper)
|
26
|
+
threads << Thread.new do
|
27
|
+
begin
|
28
|
+
trooper.run
|
29
|
+
ensure
|
30
|
+
if defined?(::Mongoid)
|
31
|
+
::Mongoid::IdentityMap.clear
|
32
|
+
::Mongoid.session(:default).disconnect
|
34
33
|
end
|
35
34
|
end
|
36
35
|
end
|
@@ -41,14 +40,21 @@ module Stormtroopers
|
|
41
40
|
threads.each(&:join)
|
42
41
|
end
|
43
42
|
|
43
|
+
def cleanup
|
44
|
+
threads.reject!{ |thread| !thread.alive? }
|
45
|
+
end
|
46
|
+
|
44
47
|
def logger
|
45
48
|
Stormtroopers::Manager.logger
|
46
49
|
end
|
47
50
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
51
|
+
class << self
|
52
|
+
def factory_class(config)
|
53
|
+
raise ArgumentError, "Factory class or type must be defined" if config[:factory][:class].blank? && config[:factory][:type].blank?
|
54
|
+
class_name ||= config[:factory].delete(:class)
|
55
|
+
class_name ||= "stormtroopers/#{config[:factory].delete(:type)}_factory".camelize
|
56
|
+
class_name.constantize
|
57
|
+
end
|
52
58
|
end
|
53
59
|
end
|
54
60
|
end
|
@@ -16,20 +16,18 @@ module Stormtroopers
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def exception(exception)
|
19
|
-
|
20
|
-
|
19
|
+
# Hook for to override handling exceptions
|
20
|
+
raise exception
|
21
21
|
end
|
22
22
|
|
23
23
|
def run
|
24
|
-
|
24
|
+
before_run
|
25
25
|
task.call
|
26
|
-
|
26
|
+
after_run
|
27
27
|
rescue => e
|
28
28
|
exception(e)
|
29
29
|
end
|
30
30
|
|
31
|
-
private
|
32
|
-
|
33
31
|
def logger
|
34
32
|
Manager.logger
|
35
33
|
end
|
@@ -15,18 +15,19 @@ module Stormtroopers
|
|
15
15
|
reschedule
|
16
16
|
end
|
17
17
|
|
18
|
-
private
|
19
|
-
|
20
18
|
def reschedule
|
21
19
|
if (job.attempts += 1) < max_attempts(job)
|
22
|
-
|
23
|
-
job.run_at = time
|
20
|
+
job.run_at = job.reschedule_at
|
24
21
|
job.unlock
|
25
22
|
job.save!
|
26
23
|
else
|
27
|
-
logger.error
|
24
|
+
logger.error("PERMANENTLY removing #{job.name} because of #{job.attempts} consecutive failures.")
|
28
25
|
job.hook(:failure)
|
29
26
|
end
|
30
27
|
end
|
28
|
+
|
29
|
+
def max_attempts(job)
|
30
|
+
job.max_attempts || Delayed::Worker.max_attempts
|
31
|
+
end
|
31
32
|
end
|
32
|
-
end
|
33
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require "stormtroopers"
|
2
|
+
|
3
|
+
describe Stormtroopers::Army do
|
4
|
+
let(:army) do
|
5
|
+
Stormtroopers::Army.new(name: "Dad's Army", max_threads: 2, factory: {name: "Dad's Factory", type: :dummy})
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "#initialize" do
|
9
|
+
it "extracts the name from the options" do
|
10
|
+
army.name.should eq("Dad's Army")
|
11
|
+
end
|
12
|
+
|
13
|
+
it "extracts the max_threads from the options" do
|
14
|
+
army.max_threads.should eq(2)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "instatiates a factory using #factory_class" do
|
18
|
+
factory_class = stub
|
19
|
+
Stormtroopers::Army.any_instance.should_receive(:factory_class).and_return(factory_class)
|
20
|
+
factory_instance = stub
|
21
|
+
factory_class.should_receive(:new).with(name: "Dad's Factory", type: :dummy).and_return(factory_instance)
|
22
|
+
army.factory.should equal(factory_instance)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "when there is no factory name, the army name is used as the factory name" do
|
26
|
+
factory_class = stub
|
27
|
+
Stormtroopers::Army.any_instance.should_receive(:factory_class).and_return(factory_class)
|
28
|
+
factory_instance = stub
|
29
|
+
factory_class.should_receive(:new).with(name: "Dad's Army", type: :dummy).and_return(factory_instance)
|
30
|
+
army = Stormtroopers::Army.new(name: "Dad's Army", max_threads: 2, factory: {type: :dummy})
|
31
|
+
army.factory.should equal(factory_instance)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#cleanup" do
|
36
|
+
it "removes threads that are not alive" do
|
37
|
+
live_thread_stub = stub(:alive? => true)
|
38
|
+
dead_thread_stub = stub(:alive? => false)
|
39
|
+
army.stub(:threads).and_return([live_thread_stub, dead_thread_stub])
|
40
|
+
army.cleanup
|
41
|
+
army.threads.should eq([live_thread_stub])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "#manage" do
|
46
|
+
it "cleans up" do
|
47
|
+
army.should_receive(:cleanup)
|
48
|
+
army.manage
|
49
|
+
end
|
50
|
+
|
51
|
+
it "produces a trooper and runs it if not at max_threads" do
|
52
|
+
trooper = stub
|
53
|
+
army.factory.should_receive(:produce).and_return(trooper)
|
54
|
+
army.should_receive(:run_trooper).with(trooper)
|
55
|
+
army.manage
|
56
|
+
end
|
57
|
+
|
58
|
+
it "does not produce a trooper if at max_threads" do
|
59
|
+
thread_stub = stub(:alive? => true)
|
60
|
+
army.stub(:threads).and_return([thread_stub, thread_stub])
|
61
|
+
army.factory.should_not_receive(:produce)
|
62
|
+
army.manage
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "#run_trooper" do
|
67
|
+
it "creates a new thread and runs the trooper in it" do
|
68
|
+
trooper = mock
|
69
|
+
trooper.should_receive(:run)
|
70
|
+
Thread.should_receive(:new).and_yield
|
71
|
+
army.run_trooper(trooper)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "cleans up the Mongoid environment if Mongoid is defined" do
|
75
|
+
stub_const("Mongoid", Class.new)
|
76
|
+
stub_const("Mongoid::IdentityMap", Class.new)
|
77
|
+
Mongoid::IdentityMap.should_receive(:clear)
|
78
|
+
mongoid_session = stub
|
79
|
+
Mongoid.should_receive(:session).with(:default).and_return(mongoid_session)
|
80
|
+
mongoid_session.should_receive(:disconnect)
|
81
|
+
trooper = mock
|
82
|
+
trooper.should_receive(:run)
|
83
|
+
Thread.should_receive(:new).and_yield
|
84
|
+
army.run_trooper(trooper)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "#finish" do
|
89
|
+
it "calls join on all threads" do
|
90
|
+
thread1 = stub
|
91
|
+
thread1.should_receive(:join)
|
92
|
+
thread2 = stub
|
93
|
+
thread2.should_receive(:join)
|
94
|
+
army.stub(:threads).and_return([thread1, thread2])
|
95
|
+
army.finish
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe "#logger" do
|
100
|
+
it "takes the logger from Stormtroopers::Manager" do
|
101
|
+
logger = stub
|
102
|
+
Stormtroopers::Manager.stub(:logger).and_return(logger)
|
103
|
+
army.logger.should equal(logger)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe ".factory_class" do
|
108
|
+
it "returns the class specified in the options" do
|
109
|
+
stub_const("MyFactory", Class.new)
|
110
|
+
Stormtroopers::Army.factory_class(factory: {class: "MyFactory"}).should equal(MyFactory)
|
111
|
+
end
|
112
|
+
|
113
|
+
it "returns a builtin factory class when type is specified in the options" do
|
114
|
+
Stormtroopers::Army.factory_class(factory: {type: :dummy}).should equal(Stormtroopers::DummyFactory)
|
115
|
+
Stormtroopers::Army.factory_class(factory: {type: :delayed_job}).should equal(Stormtroopers::DelayedJobFactory)
|
116
|
+
end
|
117
|
+
|
118
|
+
it "raises an ArgumentError when no type or class is specified" do
|
119
|
+
expect { Stormtroopers::Army.factory_class(factory: {}) }.to raise_error(ArgumentError)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "stormtroopers/factory/delayed_job"
|
2
|
+
|
3
|
+
describe Stormtroopers::DelayedJobFactory do
|
4
|
+
let(:factory) { Stormtroopers::DelayedJobFactory.new }
|
5
|
+
let(:worker) { stub }
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
stub_const("Delayed::Worker", Class.new)
|
9
|
+
Delayed::Worker.stub(:new).and_return(worker)
|
10
|
+
worker.stub(:name=).and_return(nil)
|
11
|
+
worker.stub(:name).and_return(nil)
|
12
|
+
stub_const("Delayed::Job", Class.new)
|
13
|
+
Delayed::Job.stub(:reserve)
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#produce" do
|
17
|
+
it "instantiates a new worker" do
|
18
|
+
Delayed::Worker.should_receive(:new).and_return(worker)
|
19
|
+
factory.produce
|
20
|
+
end
|
21
|
+
|
22
|
+
it "assigns the worker a name with random value" do
|
23
|
+
Time.stub_chain(:now, :utc, :to_f).and_return("1234")
|
24
|
+
factory.should_receive(:rand).and_return(500)
|
25
|
+
worker.should_receive(:name=).with("rand 1234 500")
|
26
|
+
factory.produce
|
27
|
+
end
|
28
|
+
|
29
|
+
it "reserves a job and instantiates a trooper" do
|
30
|
+
job = stub.as_null_object
|
31
|
+
trooper = stub
|
32
|
+
Delayed::Job.should_receive(:reserve).and_return(job)
|
33
|
+
Stormtroopers::DelayedJobTrooper.should_receive(:new).and_return(trooper)
|
34
|
+
factory.produce.should equal(trooper)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "does not instantiate a trooper if it doesn't find a job to reserve" do
|
38
|
+
Delayed::Job.should_receive(:reserve).and_return(nil)
|
39
|
+
factory.produce.should be_nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "stormtroopers/factory"
|
2
|
+
|
3
|
+
describe Stormtroopers::Factory do
|
4
|
+
describe "#options" do
|
5
|
+
it "exposes the options passed in during #initialize" do
|
6
|
+
options = {key1: "value1", key2: "value2"}
|
7
|
+
factory = Stormtroopers::Factory.new(options)
|
8
|
+
factory.options.should equal(options)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#produce" do
|
13
|
+
it "is not implemented" do
|
14
|
+
factory = Stormtroopers::Factory.new
|
15
|
+
expect { factory.produce }.to raise_error(NotImplementedError)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#logger" do
|
20
|
+
it "uses the Stormtroopers::Manager#logger" do
|
21
|
+
stub_const("Stormtroopers::Manager", Class.new)
|
22
|
+
logger = stub
|
23
|
+
Stormtroopers::Manager.stub(:logger).and_return(logger)
|
24
|
+
factory = Stormtroopers::Factory.new
|
25
|
+
factory.logger.should equal(logger)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require "stormtroopers/trooper/delayed_job"
|
2
|
+
|
3
|
+
describe Stormtroopers::DelayedJobTrooper do
|
4
|
+
let(:job) { stub.as_null_object }
|
5
|
+
let(:trooper) { Stormtroopers::DelayedJobTrooper.new(job) }
|
6
|
+
|
7
|
+
describe "#initialize" do
|
8
|
+
it "accepts a job and exposes it as #job" do
|
9
|
+
job = stub.as_null_object
|
10
|
+
trooper = Stormtroopers::DelayedJobTrooper.new(job)
|
11
|
+
trooper.job.should equal(job)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#run" do
|
16
|
+
context "a succesful job" do
|
17
|
+
it "calls invoke job.invoke_job and then job.destroy" do
|
18
|
+
job.should_receive(:invoke_job)
|
19
|
+
job.should_receive(:destroy)
|
20
|
+
trooper.run
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "a failing job" do
|
25
|
+
it "sets job.last_error and reschedules the job" do
|
26
|
+
job.should_receive(:invoke_job) { raise "Oops!" }
|
27
|
+
job.should_not_receive(:destroy)
|
28
|
+
job.should_receive(:last_error=).with(match("Oops!"))
|
29
|
+
trooper.should_receive(:reschedule)
|
30
|
+
trooper.run
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#reschedule" do
|
36
|
+
context "job has not reached max attempts" do
|
37
|
+
it "should reschedule and unlock" do
|
38
|
+
job.stub(:attempts).and_return(1)
|
39
|
+
job.stub(:max_attempts).and_return(3)
|
40
|
+
job.should_receive(:run_at=)
|
41
|
+
job.should_receive(:unlock)
|
42
|
+
job.should_receive(:save!)
|
43
|
+
trooper.reschedule
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "job has reached max attempts" do
|
48
|
+
it "should fail the job" do
|
49
|
+
job.stub(:attempts).and_return(3)
|
50
|
+
job.stub(:max_attempts).and_return(3)
|
51
|
+
job.should_receive(:hook).with(:failure)
|
52
|
+
trooper.reschedule
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require "stormtroopers/trooper"
|
2
|
+
|
3
|
+
describe Stormtroopers::Trooper do
|
4
|
+
before(:each) do
|
5
|
+
stub_const("Stormtroopers::Manager", Class.new)
|
6
|
+
logger = stub
|
7
|
+
Stormtroopers::Manager.stub(:logger).and_return(logger)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "#initialize" do
|
11
|
+
it "it accepts parameters and exposes them through #parameters" do
|
12
|
+
parameters = {key1: "value1", key2: "value2"}
|
13
|
+
trooper = Stormtroopers::Trooper.new(parameters)
|
14
|
+
trooper.parameters.should equal(parameters)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "accepts a task block and exposes it through #task" do
|
18
|
+
task = lambda { puts "This is a task" }
|
19
|
+
trooper = Stormtroopers::Trooper.new({}, &task)
|
20
|
+
trooper.task.should equal(task)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#run" do
|
25
|
+
let(:task) { lambda { puts "This is a task" } }
|
26
|
+
let(:trooper) { Stormtroopers::Trooper.new({}, &task) }
|
27
|
+
|
28
|
+
it "calls the before_run hook" do
|
29
|
+
trooper.should_receive(:before_run)
|
30
|
+
trooper.run
|
31
|
+
end
|
32
|
+
|
33
|
+
it "calls call on the task" do
|
34
|
+
task.should_receive(:call)
|
35
|
+
trooper.run
|
36
|
+
end
|
37
|
+
|
38
|
+
it "calls the after_run hook" do
|
39
|
+
trooper.should_receive(:after_run)
|
40
|
+
trooper.run
|
41
|
+
end
|
42
|
+
|
43
|
+
it "when the task raises an exception the exception hook is called" do
|
44
|
+
task.stub(:call) { raise "Oops" }
|
45
|
+
trooper.should_receive(:exception)
|
46
|
+
trooper.run
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "#logger" do
|
51
|
+
it "uses the Stormtroopers::Manager#logger" do
|
52
|
+
trooper = Stormtroopers::Trooper.new
|
53
|
+
trooper.logger.should equal(Stormtroopers::Manager.logger)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stormtroopers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2012-11-
|
13
|
+
date: 2012-11-13 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activesupport
|
@@ -76,7 +76,12 @@ files:
|
|
76
76
|
- lib/stormtroopers/trooper/delayed_job.rb
|
77
77
|
- lib/stormtroopers/trooper/dummy.rb
|
78
78
|
- lib/stormtroopers/version.rb
|
79
|
+
- spec/stormtroopers/army_spec.rb
|
80
|
+
- spec/stormtroopers/factory/delayed_job_spec.rb
|
81
|
+
- spec/stormtroopers/factory_spec.rb
|
79
82
|
- spec/stormtroopers/manager_spec.rb
|
83
|
+
- spec/stormtroopers/trooper/delayed_job_spec.rb
|
84
|
+
- spec/stormtroopers/trooper_spec.rb
|
80
85
|
- stormtroopers.gemspec
|
81
86
|
- vagrant-provision
|
82
87
|
homepage: http://github.com/socialreferral/stormtroopers
|
@@ -106,4 +111,9 @@ signing_key:
|
|
106
111
|
specification_version: 3
|
107
112
|
summary: Execute delayed jobs in a threaded jruby environment
|
108
113
|
test_files:
|
114
|
+
- spec/stormtroopers/army_spec.rb
|
115
|
+
- spec/stormtroopers/factory/delayed_job_spec.rb
|
116
|
+
- spec/stormtroopers/factory_spec.rb
|
109
117
|
- spec/stormtroopers/manager_spec.rb
|
118
|
+
- spec/stormtroopers/trooper/delayed_job_spec.rb
|
119
|
+
- spec/stormtroopers/trooper_spec.rb
|