mattmatt-cap-ext-parallelize 0.1.1
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/README.md +58 -0
- data/Rakefile +33 -0
- data/VERSION.yml +4 -0
- data/lib/cap_ext_parallelize.rb +9 -0
- data/lib/capistrano/configuration/extensions/actions/invocation.rb +82 -0
- data/lib/capistrano/configuration/extensions/connections.rb +55 -0
- data/lib/capistrano/configuration/extensions/execution.rb +38 -0
- data/test/parallel_invocation_test.rb +226 -0
- data/test/utils.rb +38 -0
- metadata +76 -0
data/README.md
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
cap-ext-parallelize - A Capistrano extension for parallel task execution
|
2
|
+
=============
|
3
|
+
|
4
|
+
Imagine you want to restart several processes, either on one or on different
|
5
|
+
servers, but that flingin flangin Capistrano just doesn't want to run all the
|
6
|
+
restart tasks in parallel.
|
7
|
+
|
8
|
+
I know what you're saying, Capistrano already has a command called `parallel`.
|
9
|
+
That should do right? Not exactly, we wanted to be able to run complete tasks,
|
10
|
+
no arbitrary blocks in parallel. The command `parallel` is only able to run
|
11
|
+
specific shell commands, and it looks weird when you want to run several of
|
12
|
+
them on only one host.
|
13
|
+
|
14
|
+
We were inspired by the syntax though, so when you want to run arbitrary blocks
|
15
|
+
in your Capistrano tasks, you can do it like this:
|
16
|
+
|
17
|
+
parallelize do |session|
|
18
|
+
session.run {deploy.restart}
|
19
|
+
session.run {queue.restart}
|
20
|
+
session.run {daemon.restart}
|
21
|
+
end
|
22
|
+
|
23
|
+
Every task will be run in its own thread, opening a new connection to the server.
|
24
|
+
Because of this you should be aware of potential resource exhaustion. You can
|
25
|
+
limit the number of threads in two ways, either set a variable (it defaults
|
26
|
+
to 10):
|
27
|
+
|
28
|
+
set :parallelize_thread_count, 10
|
29
|
+
|
30
|
+
Or specify it with a parameter:
|
31
|
+
|
32
|
+
parallelize(5) do
|
33
|
+
...
|
34
|
+
end
|
35
|
+
|
36
|
+
If one of your tasks ran in a transaction block and issued a rollback,
|
37
|
+
parallelize will rollback all other threads, if they have rollback statements
|
38
|
+
defined.
|
39
|
+
|
40
|
+
Installation
|
41
|
+
============
|
42
|
+
|
43
|
+
1. Install the gem
|
44
|
+
|
45
|
+
gem install -s http://gems.github.com mattmatt-cap-ext-parallelize
|
46
|
+
|
47
|
+
2. Add the following line to your Capfile
|
48
|
+
|
49
|
+
require 'cap\_ext\_parallelize'
|
50
|
+
|
51
|
+
3. There is no step 3
|
52
|
+
|
53
|
+
License
|
54
|
+
=======
|
55
|
+
|
56
|
+
(c) 2009 Mathias Meyer, Jonathan Weiss
|
57
|
+
|
58
|
+
MIT-License
|
data/Rakefile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'jeweler'
|
7
|
+
Jeweler::Tasks.new do |s|
|
8
|
+
s.name = 'cap-ext-parallelize'
|
9
|
+
s.summary = 'A drop-in replacement for Capistrano to fire off Webistrano deployments transparently without losing the joy of using the cap command.'
|
10
|
+
s.email = 'meyer@paperplanes.de'
|
11
|
+
s.homepage = 'http://github.com/mattmatt/cap-ext-parallelize'
|
12
|
+
s.authors = ["Mathias Meyer"]
|
13
|
+
s.files = FileList["[A-Z]*", "{lib,test}/**/*"]
|
14
|
+
s.add_dependency 'capistrano'
|
15
|
+
end
|
16
|
+
rescue LoadError
|
17
|
+
puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
18
|
+
end
|
19
|
+
|
20
|
+
desc "Default Task"
|
21
|
+
task :default => ["test"]
|
22
|
+
|
23
|
+
desc "Runs the unit tests"
|
24
|
+
task :test => "test:unit"
|
25
|
+
|
26
|
+
namespace :test do
|
27
|
+
desc "Unit tests"
|
28
|
+
Rake::TestTask.new(:unit) do |t|
|
29
|
+
t.libs << 'test/unit'
|
30
|
+
t.pattern = "test/*_shoulda.rb"
|
31
|
+
t.verbose = true
|
32
|
+
end
|
33
|
+
end
|
data/VERSION.yml
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/capistrano/configuration/extensions/actions/invocation"
|
2
|
+
require "#{File.dirname(__FILE__)}/capistrano/configuration/extensions/connections"
|
3
|
+
require "#{File.dirname(__FILE__)}/capistrano/configuration/extensions/execution"
|
4
|
+
|
5
|
+
class Capistrano::Configuration
|
6
|
+
include Capistrano::Configuration::Extensions::Actions::Invocation
|
7
|
+
include Capistrano::Configuration::Extensions::Connections
|
8
|
+
include Capistrano::Configuration::Extensions::Execution
|
9
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Capistrano
|
2
|
+
class Configuration
|
3
|
+
module Extensions
|
4
|
+
module Actions
|
5
|
+
module Invocation
|
6
|
+
class BlockProxy
|
7
|
+
attr_accessor :blocks
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@blocks = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def run(&block)
|
14
|
+
blocks << block
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def parallelize(thread_count = nil)
|
19
|
+
set :parallelize_thread_count, 10 unless respond_to?(:parallelize_thread_count)
|
20
|
+
|
21
|
+
proxy = BlockProxy.new
|
22
|
+
yield proxy
|
23
|
+
|
24
|
+
logger.info "Running #{proxy.blocks.size} threads in chunks of #{thread_count || parallelize_thread_count}"
|
25
|
+
|
26
|
+
run_parallelize_loop(proxy, thread_count || parallelize_thread_count)
|
27
|
+
end
|
28
|
+
|
29
|
+
def run_parallelize_loop(proxy, thread_count)
|
30
|
+
batch = 1
|
31
|
+
all_threads = []
|
32
|
+
proxy.blocks.each_slice(thread_count) do |chunk|
|
33
|
+
logger.info "Running batch number #{batch}"
|
34
|
+
threads = run_in_threads(chunk)
|
35
|
+
all_threads << threads
|
36
|
+
wait_for(threads)
|
37
|
+
rollback_all_threads(all_threads.flatten) and return if threads.any? {|t| t[:rolled_back] || t[:exception_raised]}
|
38
|
+
batch += 1
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def run_in_threads(blocks)
|
43
|
+
blocks.collect do |blk|
|
44
|
+
thread = Thread.new do
|
45
|
+
logger.info "Running block in background thread"
|
46
|
+
blk.call
|
47
|
+
end
|
48
|
+
begin
|
49
|
+
thread.run
|
50
|
+
rescue ThreadError
|
51
|
+
thread[:exception_raised] = $!
|
52
|
+
end
|
53
|
+
thread
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def wait_for(threads)
|
58
|
+
threads.each do |thread|
|
59
|
+
begin
|
60
|
+
thread.join
|
61
|
+
rescue
|
62
|
+
logger.important "Subthread failed: #{$!.message}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def rollback_all_threads(threads)
|
68
|
+
Thread.new do
|
69
|
+
threads.select {|t| !t[:rolled_back]}.each do |thread|
|
70
|
+
Thread.current[:rollback_requests] = thread[:rollback_requests]
|
71
|
+
rollback!
|
72
|
+
end
|
73
|
+
end.join
|
74
|
+
rollback! # Rolling back main thread too
|
75
|
+
true
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Capistrano
|
2
|
+
class Configuration
|
3
|
+
module Extensions
|
4
|
+
# Thread-safe(r) version of the Capistrano default
|
5
|
+
# connection handling.
|
6
|
+
module Connections
|
7
|
+
def initialize_with_connections(*args) #:nodoc:
|
8
|
+
initialize_without_connections(*args)
|
9
|
+
Thread.current[:sessions] = {}
|
10
|
+
Thread.current[:failed_sessions] = []
|
11
|
+
end
|
12
|
+
|
13
|
+
# Indicate that the given server could not be connected to.
|
14
|
+
def failed!(server)
|
15
|
+
Thread.current[:failed_sessions] << server
|
16
|
+
end
|
17
|
+
|
18
|
+
# A hash of the SSH sessions that are currently open and available.
|
19
|
+
# Because sessions are constructed lazily, this will only contain
|
20
|
+
# connections to those servers that have been the targets of one or more
|
21
|
+
# executed tasks.
|
22
|
+
def sessions
|
23
|
+
Thread.current[:sessions] ||= {}
|
24
|
+
end
|
25
|
+
|
26
|
+
# Query whether previous connection attempts to the given server have
|
27
|
+
# failed.
|
28
|
+
def has_failed?(server)
|
29
|
+
Thread.current[:failed_sessions].include?(server)
|
30
|
+
end
|
31
|
+
|
32
|
+
def teardown_connections_to(servers)
|
33
|
+
servers.each do |server|
|
34
|
+
sessions[server].close
|
35
|
+
sessions.delete(server)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def establish_connection_to(server, failures=nil)
|
41
|
+
current_thread = Thread.current
|
42
|
+
Thread.new { safely_establish_connection_to(server, current_thread, failures) }
|
43
|
+
end
|
44
|
+
|
45
|
+
def safely_establish_connection_to(server, thread, failures=nil)
|
46
|
+
thread[:sessions] ||= {}
|
47
|
+
thread[:sessions][server] ||= connection_factory.connect_to(server)
|
48
|
+
rescue Exception => err
|
49
|
+
raise unless failures
|
50
|
+
failures << { :server => server, :error => err }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Capistrano
|
2
|
+
class Configuration
|
3
|
+
module Extensions
|
4
|
+
module Execution
|
5
|
+
def task_call_frames
|
6
|
+
Thread.current[:task_call_frames] ||= []
|
7
|
+
end
|
8
|
+
|
9
|
+
def rollback_requests=(rollback_requests)
|
10
|
+
Thread.current[:rollback_requests] = rollback_requests
|
11
|
+
end
|
12
|
+
|
13
|
+
def rollback_requests
|
14
|
+
Thread.current[:rollback_requests]
|
15
|
+
end
|
16
|
+
|
17
|
+
def current_task
|
18
|
+
all_task_call_frames = Thread.main[:task_call_frames] + task_call_frames
|
19
|
+
return nil if all_task_call_frames.empty?
|
20
|
+
all_task_call_frames.last.task
|
21
|
+
end
|
22
|
+
|
23
|
+
def transaction
|
24
|
+
super do
|
25
|
+
self.rollback_requests = [] unless transaction?
|
26
|
+
yield
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def rollback!
|
31
|
+
return if Thread.current[:rollback_requests].nil?
|
32
|
+
Thread.current[:rolled_back] = true
|
33
|
+
super
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
require "utils"
|
2
|
+
require "capistrano/task_definition"
|
3
|
+
require "capistrano/configuration"
|
4
|
+
require "#{File.join(File.dirname(__FILE__), '..', 'lib')}/cap_ext_parallelize"
|
5
|
+
|
6
|
+
class ConfigurationActionsParallelInvocationTest < Test::Unit::TestCase
|
7
|
+
class MockConfig
|
8
|
+
attr_reader :options
|
9
|
+
attr_accessor :debug
|
10
|
+
attr_accessor :dry_run
|
11
|
+
attr_reader :tasks, :namespaces, :fully_qualified_name, :parent
|
12
|
+
attr_reader :state, :original_initialize_called
|
13
|
+
attr_accessor :logger, :default_task
|
14
|
+
attr_accessor :parallelize_thread_count
|
15
|
+
|
16
|
+
def initialize(options)
|
17
|
+
@original_initialize_called = true
|
18
|
+
@tasks = {}
|
19
|
+
@namespaces = {}
|
20
|
+
@state = {}
|
21
|
+
@fully_qualified_name = options[:fqn]
|
22
|
+
@parent = options[:parent]
|
23
|
+
@logger = options.delete(:logger)
|
24
|
+
@options = {}
|
25
|
+
@parallelize_thread_count = 10
|
26
|
+
end
|
27
|
+
|
28
|
+
def [](*args)
|
29
|
+
@options[*args]
|
30
|
+
end
|
31
|
+
|
32
|
+
def set(name, value)
|
33
|
+
@options[name] = value
|
34
|
+
end
|
35
|
+
|
36
|
+
def fetch(*args)
|
37
|
+
@options.fetch(*args)
|
38
|
+
end
|
39
|
+
|
40
|
+
include Capistrano::Configuration::Execution
|
41
|
+
include Capistrano::Configuration::Actions::Invocation
|
42
|
+
include Capistrano::Configuration::Extensions::Execution
|
43
|
+
include Capistrano::Configuration::Extensions::Actions::Invocation
|
44
|
+
end
|
45
|
+
|
46
|
+
def setup
|
47
|
+
@config = MockConfig.new(:logger => stub(:debug => nil, :info => nil, :important => nil))
|
48
|
+
@original_io_proc = MockConfig.default_io_proc
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_parallelize_should_run_all_collected_tasks
|
52
|
+
aaa = new_task(@config, :aaa) do
|
53
|
+
parallelize do |session|
|
54
|
+
session.run {(state[:has_been_run] ||= []) << :first}
|
55
|
+
session.run {(state[:has_been_run] ||= []) << :second}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
@config.execute_task(aaa)
|
59
|
+
assert @config.state[:has_been_run].include?(:first)
|
60
|
+
assert @config.state[:has_been_run].include?(:second)
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_parallelize_should_rollback_all_threads_when_one_thread_raises_error
|
64
|
+
ccc = new_task(@config, :ccc) do
|
65
|
+
on_rollback {(state[:rollback] ||= []) << :first}
|
66
|
+
raise "boom"
|
67
|
+
end
|
68
|
+
|
69
|
+
eee = new_task(@config, :eee) do
|
70
|
+
on_rollback {(state[:rollback] ||= []) << :second}
|
71
|
+
end
|
72
|
+
|
73
|
+
ddd = new_task(@config, :ddd) do
|
74
|
+
transaction {execute_task(eee)}
|
75
|
+
end
|
76
|
+
|
77
|
+
bbb = new_task(@config, :bbb) {transaction {execute_task(ccc)}}
|
78
|
+
|
79
|
+
aaa = new_task(@config, :aaa) do
|
80
|
+
on_rollback {puts 'rolled back'}
|
81
|
+
parallelize do |session|
|
82
|
+
session.run {execute_task(bbb)}
|
83
|
+
session.run {execute_task(ddd)}
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
@config.execute_task(aaa)
|
88
|
+
assert @config.state[:rollback].include?(:first)
|
89
|
+
assert @config.state[:rollback].include?(:second)
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_parallelize_should_rollback_only_run_threads_when_one_thread_raises_error
|
93
|
+
ccc = new_task(@config, :ccc) do
|
94
|
+
on_rollback {(state[:rollback] ||= []) << :first}
|
95
|
+
raise "boom"
|
96
|
+
end
|
97
|
+
|
98
|
+
eee = new_task(@config, :eee) do
|
99
|
+
on_rollback {(state[:rollback] ||= []) << :second}
|
100
|
+
end
|
101
|
+
|
102
|
+
ddd = new_task(@config, :ddd) do
|
103
|
+
transaction {execute_task(eee)}
|
104
|
+
end
|
105
|
+
|
106
|
+
bbb = new_task(@config, :bbb) {transaction {execute_task(ccc)}}
|
107
|
+
|
108
|
+
aaa = new_task(@config, :aaa) do
|
109
|
+
on_rollback {puts 'rolled back'}
|
110
|
+
parallelize do |session|
|
111
|
+
session.run {execute_task(bbb)}
|
112
|
+
session.run {execute_task(ddd)}
|
113
|
+
end
|
114
|
+
end
|
115
|
+
@config.parallelize_thread_count = 1
|
116
|
+
@config.execute_task(aaa)
|
117
|
+
assert @config.state[:rollback].include?(:first)
|
118
|
+
assert !@config.state[:rollback].include?(:second)
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_parallelize_should_rollback_all_threads_when_one_thread_raises_error
|
122
|
+
ccc = new_task(@config, :ccc) do
|
123
|
+
on_rollback {(state[:rollback] ||= []) << :first}
|
124
|
+
sleep 0.1
|
125
|
+
raise "boom"
|
126
|
+
end
|
127
|
+
|
128
|
+
eee = new_task(@config, :eee) do
|
129
|
+
on_rollback {(state[:rollback] ||= []) << :second}
|
130
|
+
end
|
131
|
+
|
132
|
+
ddd = new_task(@config, :ddd) do
|
133
|
+
transaction {execute_task(eee)}
|
134
|
+
end
|
135
|
+
|
136
|
+
bbb = new_task(@config, :bbb) {transaction {execute_task(ccc)}}
|
137
|
+
|
138
|
+
aaa = new_task(@config, :aaa) do
|
139
|
+
on_rollback {puts 'rolled back'}
|
140
|
+
parallelize do |session|
|
141
|
+
session.run {execute_task(bbb)}
|
142
|
+
session.run {execute_task(ddd)}
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
@config.execute_task(aaa)
|
147
|
+
assert @config.state[:rollback].include?(:first)
|
148
|
+
assert @config.state[:rollback].include?(:second)
|
149
|
+
end
|
150
|
+
|
151
|
+
def test_should_not_rollback_threads_twice
|
152
|
+
ccc = new_task(@config, :ccc) do
|
153
|
+
on_rollback {(state[:rollback] ||= []) << :first}
|
154
|
+
raise "boom"
|
155
|
+
end
|
156
|
+
|
157
|
+
eee = new_task(@config, :eee) do
|
158
|
+
on_rollback {(state[:rollback] ||= []) << :second}
|
159
|
+
end
|
160
|
+
|
161
|
+
ddd = new_task(@config, :ddd) do
|
162
|
+
transaction {execute_task(eee)}
|
163
|
+
end
|
164
|
+
|
165
|
+
bbb = new_task(@config, :bbb) {transaction {execute_task(ccc)}}
|
166
|
+
|
167
|
+
aaa = new_task(@config, :aaa) do
|
168
|
+
on_rollback {puts 'rolled back'}
|
169
|
+
parallelize do |session|
|
170
|
+
session.run {execute_task(bbb)}
|
171
|
+
session.run {execute_task(ddd)}
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
@config.execute_task(aaa)
|
176
|
+
assert_equal 2, @config.state[:rollback].size
|
177
|
+
assert @config.state[:rollback].include?(:first)
|
178
|
+
assert @config.state[:rollback].include?(:second)
|
179
|
+
end
|
180
|
+
|
181
|
+
def test_should_not_rollback_threads_twice
|
182
|
+
|
183
|
+
eee = new_task(@config, :eee) do
|
184
|
+
on_rollback {(state[:rollback] ||= []) << :second}
|
185
|
+
end
|
186
|
+
|
187
|
+
ddd = new_task(@config, :ddd) do
|
188
|
+
transaction {execute_task(eee)}
|
189
|
+
end
|
190
|
+
|
191
|
+
aaa = new_task(@config, :aaa) do
|
192
|
+
on_rollback {(state[:rollback] ||= []) << :main}
|
193
|
+
parallelize do |session|
|
194
|
+
session.run {execute_task(bbb)}
|
195
|
+
session.run {execute_task(ddd)}
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
bbb = new_task(@config, :bbb) do
|
200
|
+
transaction do
|
201
|
+
execute_task(aaa)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
@config.execute_task(bbb)
|
206
|
+
assert_equal 2, @config.state[:rollback].size
|
207
|
+
assert @config.state[:rollback].include?(:main)
|
208
|
+
assert @config.state[:rollback].include?(:second)
|
209
|
+
end
|
210
|
+
|
211
|
+
def test_should_run_each_run_block_in_separate_thread
|
212
|
+
aaa = new_task(@config, :aaa) do
|
213
|
+
parallelize do |session|
|
214
|
+
session.run {execute_task(bbb)}
|
215
|
+
session.run {execute_task(ddd)}
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
private
|
221
|
+
def new_task(namespace, name, options={}, &block)
|
222
|
+
block ||= stack_inspector
|
223
|
+
namespace.tasks[name] = Capistrano::TaskDefinition.new(name, namespace, &block)
|
224
|
+
end
|
225
|
+
|
226
|
+
end
|
data/test/utils.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
begin
|
2
|
+
require 'rubygems'
|
3
|
+
gem 'mocha'
|
4
|
+
rescue LoadError
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'test/unit'
|
8
|
+
require 'mocha'
|
9
|
+
require 'capistrano/server_definition'
|
10
|
+
|
11
|
+
module TestExtensions
|
12
|
+
def server(host, options={})
|
13
|
+
Capistrano::ServerDefinition.new(host, options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def namespace(fqn=nil)
|
17
|
+
space = stub(:roles => {}, :fully_qualified_name => fqn, :default_task => nil)
|
18
|
+
yield(space) if block_given?
|
19
|
+
space
|
20
|
+
end
|
21
|
+
|
22
|
+
def role(space, name, *args)
|
23
|
+
opts = args.last.is_a?(Hash) ? args.pop : {}
|
24
|
+
space.roles[name] ||= []
|
25
|
+
space.roles[name].concat(args.map { |h| Capistrano::ServerDefinition.new(h, opts) })
|
26
|
+
end
|
27
|
+
|
28
|
+
def new_task(name, namespace=@namespace, options={}, &block)
|
29
|
+
block ||= Proc.new {}
|
30
|
+
task = Capistrano::TaskDefinition.new(name, namespace, options, &block)
|
31
|
+
assert_equal block, task.body
|
32
|
+
return task
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class Test::Unit::TestCase
|
37
|
+
include TestExtensions
|
38
|
+
end
|
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mattmatt-cap-ext-parallelize
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mathias Meyer
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-03-19 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: capistrano
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description:
|
26
|
+
email: meyer@paperplanes.de
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files: []
|
32
|
+
|
33
|
+
files:
|
34
|
+
- Rakefile
|
35
|
+
- README.md
|
36
|
+
- VERSION.yml
|
37
|
+
- lib/cap_ext_parallelize.rb
|
38
|
+
- lib/capistrano
|
39
|
+
- lib/capistrano/configuration
|
40
|
+
- lib/capistrano/configuration/extensions
|
41
|
+
- lib/capistrano/configuration/extensions/actions
|
42
|
+
- lib/capistrano/configuration/extensions/actions/extensions
|
43
|
+
- lib/capistrano/configuration/extensions/actions/invocation.rb
|
44
|
+
- lib/capistrano/configuration/extensions/connections.rb
|
45
|
+
- lib/capistrano/configuration/extensions/execution.rb
|
46
|
+
- test/parallel_invocation_test.rb
|
47
|
+
- test/utils.rb
|
48
|
+
has_rdoc: true
|
49
|
+
homepage: http://github.com/mattmatt/cap-ext-parallelize
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options:
|
52
|
+
- --inline-source
|
53
|
+
- --charset=UTF-8
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: "0"
|
61
|
+
version:
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: "0"
|
67
|
+
version:
|
68
|
+
requirements: []
|
69
|
+
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 1.2.0
|
72
|
+
signing_key:
|
73
|
+
specification_version: 2
|
74
|
+
summary: A drop-in replacement for Capistrano to fire off Webistrano deployments transparently without losing the joy of using the cap command.
|
75
|
+
test_files: []
|
76
|
+
|