minmb-capistrano 2.15.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. data/.gitignore +10 -0
  2. data/.travis.yml +7 -0
  3. data/CHANGELOG +1170 -0
  4. data/Gemfile +13 -0
  5. data/README.md +94 -0
  6. data/Rakefile +11 -0
  7. data/bin/cap +4 -0
  8. data/bin/capify +92 -0
  9. data/capistrano.gemspec +40 -0
  10. data/lib/capistrano.rb +5 -0
  11. data/lib/capistrano/callback.rb +45 -0
  12. data/lib/capistrano/cli.rb +47 -0
  13. data/lib/capistrano/cli/execute.rb +85 -0
  14. data/lib/capistrano/cli/help.rb +125 -0
  15. data/lib/capistrano/cli/help.txt +81 -0
  16. data/lib/capistrano/cli/options.rb +243 -0
  17. data/lib/capistrano/cli/ui.rb +40 -0
  18. data/lib/capistrano/command.rb +303 -0
  19. data/lib/capistrano/configuration.rb +57 -0
  20. data/lib/capistrano/configuration/actions/file_transfer.rb +50 -0
  21. data/lib/capistrano/configuration/actions/inspect.rb +46 -0
  22. data/lib/capistrano/configuration/actions/invocation.rb +329 -0
  23. data/lib/capistrano/configuration/alias_task.rb +26 -0
  24. data/lib/capistrano/configuration/callbacks.rb +147 -0
  25. data/lib/capistrano/configuration/connections.rb +237 -0
  26. data/lib/capistrano/configuration/execution.rb +142 -0
  27. data/lib/capistrano/configuration/loading.rb +205 -0
  28. data/lib/capistrano/configuration/log_formatters.rb +75 -0
  29. data/lib/capistrano/configuration/namespaces.rb +223 -0
  30. data/lib/capistrano/configuration/roles.rb +77 -0
  31. data/lib/capistrano/configuration/servers.rb +116 -0
  32. data/lib/capistrano/configuration/variables.rb +127 -0
  33. data/lib/capistrano/errors.rb +19 -0
  34. data/lib/capistrano/ext/multistage.rb +64 -0
  35. data/lib/capistrano/ext/string.rb +5 -0
  36. data/lib/capistrano/extensions.rb +57 -0
  37. data/lib/capistrano/fix_rake_deprecated_dsl.rb +8 -0
  38. data/lib/capistrano/logger.rb +166 -0
  39. data/lib/capistrano/processable.rb +57 -0
  40. data/lib/capistrano/recipes/compat.rb +32 -0
  41. data/lib/capistrano/recipes/deploy.rb +625 -0
  42. data/lib/capistrano/recipes/deploy/assets.rb +201 -0
  43. data/lib/capistrano/recipes/deploy/dependencies.rb +44 -0
  44. data/lib/capistrano/recipes/deploy/local_dependency.rb +54 -0
  45. data/lib/capistrano/recipes/deploy/remote_dependency.rb +117 -0
  46. data/lib/capistrano/recipes/deploy/scm.rb +19 -0
  47. data/lib/capistrano/recipes/deploy/scm/accurev.rb +169 -0
  48. data/lib/capistrano/recipes/deploy/scm/base.rb +200 -0
  49. data/lib/capistrano/recipes/deploy/scm/bzr.rb +86 -0
  50. data/lib/capistrano/recipes/deploy/scm/cvs.rb +153 -0
  51. data/lib/capistrano/recipes/deploy/scm/darcs.rb +96 -0
  52. data/lib/capistrano/recipes/deploy/scm/git.rb +293 -0
  53. data/lib/capistrano/recipes/deploy/scm/mercurial.rb +137 -0
  54. data/lib/capistrano/recipes/deploy/scm/none.rb +55 -0
  55. data/lib/capistrano/recipes/deploy/scm/perforce.rb +152 -0
  56. data/lib/capistrano/recipes/deploy/scm/subversion.rb +121 -0
  57. data/lib/capistrano/recipes/deploy/strategy.rb +19 -0
  58. data/lib/capistrano/recipes/deploy/strategy/base.rb +92 -0
  59. data/lib/capistrano/recipes/deploy/strategy/checkout.rb +20 -0
  60. data/lib/capistrano/recipes/deploy/strategy/copy.rb +338 -0
  61. data/lib/capistrano/recipes/deploy/strategy/export.rb +20 -0
  62. data/lib/capistrano/recipes/deploy/strategy/remote.rb +52 -0
  63. data/lib/capistrano/recipes/deploy/strategy/remote_cache.rb +57 -0
  64. data/lib/capistrano/recipes/deploy/strategy/unshared_remote_cache.rb +21 -0
  65. data/lib/capistrano/recipes/standard.rb +37 -0
  66. data/lib/capistrano/recipes/templates/maintenance.rhtml +53 -0
  67. data/lib/capistrano/role.rb +102 -0
  68. data/lib/capistrano/server_definition.rb +56 -0
  69. data/lib/capistrano/shell.rb +265 -0
  70. data/lib/capistrano/ssh.rb +95 -0
  71. data/lib/capistrano/task_definition.rb +77 -0
  72. data/lib/capistrano/transfer.rb +218 -0
  73. data/lib/capistrano/version.rb +11 -0
  74. data/test/cli/execute_test.rb +132 -0
  75. data/test/cli/help_test.rb +165 -0
  76. data/test/cli/options_test.rb +329 -0
  77. data/test/cli/ui_test.rb +28 -0
  78. data/test/cli_test.rb +17 -0
  79. data/test/command_test.rb +322 -0
  80. data/test/configuration/actions/file_transfer_test.rb +61 -0
  81. data/test/configuration/actions/inspect_test.rb +76 -0
  82. data/test/configuration/actions/invocation_test.rb +288 -0
  83. data/test/configuration/alias_task_test.rb +118 -0
  84. data/test/configuration/callbacks_test.rb +201 -0
  85. data/test/configuration/connections_test.rb +439 -0
  86. data/test/configuration/execution_test.rb +175 -0
  87. data/test/configuration/loading_test.rb +148 -0
  88. data/test/configuration/namespace_dsl_test.rb +332 -0
  89. data/test/configuration/roles_test.rb +157 -0
  90. data/test/configuration/servers_test.rb +183 -0
  91. data/test/configuration/variables_test.rb +190 -0
  92. data/test/configuration_test.rb +77 -0
  93. data/test/deploy/local_dependency_test.rb +76 -0
  94. data/test/deploy/remote_dependency_test.rb +146 -0
  95. data/test/deploy/scm/accurev_test.rb +23 -0
  96. data/test/deploy/scm/base_test.rb +55 -0
  97. data/test/deploy/scm/bzr_test.rb +51 -0
  98. data/test/deploy/scm/darcs_test.rb +37 -0
  99. data/test/deploy/scm/git_test.rb +221 -0
  100. data/test/deploy/scm/mercurial_test.rb +134 -0
  101. data/test/deploy/scm/none_test.rb +35 -0
  102. data/test/deploy/scm/perforce_test.rb +23 -0
  103. data/test/deploy/scm/subversion_test.rb +40 -0
  104. data/test/deploy/strategy/copy_test.rb +360 -0
  105. data/test/extensions_test.rb +69 -0
  106. data/test/fixtures/cli_integration.rb +5 -0
  107. data/test/fixtures/config.rb +5 -0
  108. data/test/fixtures/custom.rb +3 -0
  109. data/test/logger_formatting_test.rb +149 -0
  110. data/test/logger_test.rb +134 -0
  111. data/test/recipes_test.rb +25 -0
  112. data/test/role_test.rb +11 -0
  113. data/test/server_definition_test.rb +121 -0
  114. data/test/shell_test.rb +96 -0
  115. data/test/ssh_test.rb +113 -0
  116. data/test/task_definition_test.rb +117 -0
  117. data/test/transfer_test.rb +168 -0
  118. data/test/utils.rb +37 -0
  119. metadata +316 -0
@@ -0,0 +1,439 @@
1
+ require "utils"
2
+ require 'capistrano/configuration/connections'
3
+
4
+ class ConfigurationConnectionsTest < Test::Unit::TestCase
5
+ class MockConfig
6
+ attr_reader :original_initialize_called
7
+ attr_reader :values
8
+ attr_reader :dry_run
9
+ attr_accessor :current_task
10
+
11
+ def initialize
12
+ @original_initialize_called = true
13
+ @values = {}
14
+ end
15
+
16
+ def fetch(*args)
17
+ @values.fetch(*args)
18
+ end
19
+
20
+ def [](key)
21
+ @values[key]
22
+ end
23
+
24
+ def exists?(key)
25
+ @values.key?(key)
26
+ end
27
+
28
+ include Capistrano::Configuration::Connections
29
+ end
30
+
31
+ def setup
32
+ @config = MockConfig.new
33
+ @config.stubs(:logger).returns(stub_everything)
34
+ Net::SSH.stubs(:configuration_for).returns({})
35
+ @ssh_options = {
36
+ :user => "user",
37
+ :port => 8080,
38
+ :password => "g00b3r",
39
+ :ssh_options => { :debug => :verbose }
40
+ }
41
+ end
42
+
43
+ def test_initialize_should_initialize_collections_and_call_original_initialize
44
+ assert @config.original_initialize_called
45
+ assert @config.sessions.empty?
46
+ end
47
+
48
+ def test_connection_factory_should_return_default_connection_factory_instance
49
+ factory = @config.connection_factory
50
+ assert_instance_of Capistrano::Configuration::Connections::DefaultConnectionFactory, factory
51
+ end
52
+
53
+ def test_connection_factory_instance_should_be_cached
54
+ assert_same @config.connection_factory, @config.connection_factory
55
+ end
56
+
57
+ def test_default_connection_factory_honors_config_options
58
+ server = server("capistrano")
59
+ Capistrano::SSH.expects(:connect).with(server, @config).returns(:session)
60
+ assert_equal :session, @config.connection_factory.connect_to(server)
61
+ end
62
+
63
+ def test_should_connect_through_gateway_if_gateway_variable_is_set
64
+ @config.values[:gateway] = "j@gateway"
65
+ Net::SSH::Gateway.expects(:new).with("gateway", "j", :password => nil, :auth_methods => %w(publickey hostbased), :config => false).returns(stub_everything)
66
+ assert_instance_of Capistrano::Configuration::Connections::GatewayConnectionFactory, @config.connection_factory
67
+ end
68
+
69
+ def test_connection_factory_as_gateway_should_honor_config_options
70
+ @config.values[:gateway] = "gateway"
71
+ @config.values.update(@ssh_options)
72
+ Net::SSH::Gateway.expects(:new).with("gateway", "user", :debug => :verbose, :port => 8080, :password => nil, :auth_methods => %w(publickey hostbased), :config => false).returns(stub_everything)
73
+ assert_instance_of Capistrano::Configuration::Connections::GatewayConnectionFactory, @config.connection_factory
74
+ end
75
+
76
+ def test_connection_factory_as_gateway_should_chain_gateways_if_gateway_variable_is_an_array
77
+ @config.values[:gateway] = ["j@gateway1", "k@gateway2"]
78
+ gateway1 = mock
79
+ Net::SSH::Gateway.expects(:new).with("gateway1", "j", :password => nil, :auth_methods => %w(publickey hostbased), :config => false).returns(gateway1)
80
+ gateway1.expects(:open).returns(65535)
81
+ Net::SSH::Gateway.expects(:new).with("127.0.0.1", "k", :port => 65535, :password => nil, :auth_methods => %w(publickey hostbased), :config => false).returns(stub_everything)
82
+ assert_instance_of Capistrano::Configuration::Connections::GatewayConnectionFactory, @config.connection_factory
83
+ end
84
+
85
+ def test_connection_factory_as_gateway_should_chain_gateways_if_gateway_variable_is_a_hash
86
+ @config.values[:gateway] = { ["j@gateway1", "k@gateway2"] => :default }
87
+ gateway1 = mock
88
+ Net::SSH::Gateway.expects(:new).with("gateway1", "j", :password => nil, :auth_methods => %w(publickey hostbased), :config => false).returns(gateway1)
89
+ gateway1.expects(:open).returns(65535)
90
+ Net::SSH::Gateway.expects(:new).with("127.0.0.1", "k", :port => 65535, :password => nil, :auth_methods => %w(publickey hostbased), :config => false).returns(stub_everything)
91
+ assert_instance_of Capistrano::Configuration::Connections::GatewayConnectionFactory, @config.connection_factory
92
+ end
93
+
94
+ def test_connection_factory_as_gateway_should_share_gateway_between_connections
95
+ @config.values[:gateway] = "j@gateway"
96
+ Net::SSH::Gateway.expects(:new).once.with("gateway", "j", :password => nil, :auth_methods => %w(publickey hostbased), :config => false).returns(stub_everything)
97
+ Capistrano::SSH.stubs(:connect).returns(stub_everything)
98
+ assert_instance_of Capistrano::Configuration::Connections::GatewayConnectionFactory, @config.connection_factory
99
+ @config.establish_connections_to(server("capistrano"))
100
+ @config.establish_connections_to(server("another"))
101
+ end
102
+
103
+ def test_connection_factory_as_gateway_should_share_gateway_between_like_connections_if_gateway_variable_is_a_hash
104
+ @config.values[:gateway] = { "j@gateway" => [ "capistrano", "another"] }
105
+ Net::SSH::Gateway.expects(:new).once.with("gateway", "j", :password => nil, :auth_methods => %w(publickey hostbased), :config => false).returns(stub_everything)
106
+ Capistrano::SSH.stubs(:connect).returns(stub_everything)
107
+ assert_instance_of Capistrano::Configuration::Connections::GatewayConnectionFactory, @config.connection_factory
108
+ @config.establish_connections_to(server("capistrano"))
109
+ @config.establish_connections_to(server("another"))
110
+ end
111
+
112
+ def test_connection_factory_as_gateways_should_not_share_gateway_between_unlike_connections_if_gateway_variable_is_a_hash
113
+ @config.values[:gateway] = { "j@gateway" => [ "capistrano", "another"], "k@gateway2" => "yafhost" }
114
+ Net::SSH::Gateway.expects(:new).once.with("gateway", "j", :password => nil, :auth_methods => %w(publickey hostbased), :config => false).returns(stub_everything)
115
+ Net::SSH::Gateway.expects(:new).once.with("gateway2", "k", :password => nil, :auth_methods => %w(publickey hostbased), :config => false).returns(stub_everything)
116
+ Capistrano::SSH.stubs(:connect).returns(stub_everything)
117
+ assert_instance_of Capistrano::Configuration::Connections::GatewayConnectionFactory, @config.connection_factory
118
+ @config.establish_connections_to(server("capistrano"))
119
+ @config.establish_connections_to(server("another"))
120
+ @config.establish_connections_to(server("yafhost"))
121
+ end
122
+
123
+ def test_establish_connections_to_should_accept_a_single_nonarray_parameter
124
+ Capistrano::SSH.expects(:connect).with { |s,| s.host == "capistrano" }.returns(:success)
125
+ assert @config.sessions.empty?
126
+ @config.establish_connections_to(server("capistrano"))
127
+ assert_equal ["capistrano"], @config.sessions.keys.map(&:host)
128
+ end
129
+
130
+ def test_establish_connections_to_should_accept_an_array
131
+ Capistrano::SSH.expects(:connect).times(3).returns(:success)
132
+ assert @config.sessions.empty?
133
+ @config.establish_connections_to(%w(cap1 cap2 cap3).map { |s| server(s) })
134
+ assert_equal %w(cap1 cap2 cap3), @config.sessions.keys.sort.map(&:host)
135
+ end
136
+
137
+ def test_establish_connections_to_should_not_attempt_to_reestablish_existing_connections
138
+ Capistrano::SSH.expects(:connect).times(2).returns(:success)
139
+ @config.sessions[server("cap1")] = :ok
140
+ @config.establish_connections_to(%w(cap1 cap2 cap3).map { |s| server(s) })
141
+ assert_equal %w(cap1 cap2 cap3), @config.sessions.keys.sort.map(&:host)
142
+ end
143
+
144
+ def test_establish_connections_to_should_raise_one_connection_error_on_failure
145
+ Capistrano::SSH.expects(:connect).times(2).raises(Exception)
146
+ assert_raises(Capistrano::ConnectionError) {
147
+ @config.establish_connections_to(%w(cap1 cap2).map { |s| server(s) })
148
+ }
149
+ end
150
+
151
+ def test_connection_error_should_include_accessor_with_host_array
152
+ Capistrano::SSH.expects(:connect).times(2).raises(Exception)
153
+ begin
154
+ @config.establish_connections_to(%w(cap1 cap2).map { |s| server(s) })
155
+ flunk "expected an exception to be raised"
156
+ rescue Capistrano::ConnectionError => e
157
+ assert e.respond_to?(:hosts)
158
+ assert_equal %w(cap1 cap2), e.hosts.map { |h| h.to_s }.sort
159
+ end
160
+ end
161
+
162
+ def test_connection_error_should_only_include_failed_hosts
163
+ Capistrano::SSH.expects(:connect).with(server('cap1'), anything).raises(Exception)
164
+ Capistrano::SSH.expects(:connect).with(server('cap2'), anything).returns(:success)
165
+
166
+ begin
167
+ @config.establish_connections_to(%w(cap1 cap2).map { |s| server(s) })
168
+ flunk "expected an exception to be raised"
169
+ rescue Capistrano::ConnectionError => e
170
+ assert_equal %w(cap1), e.hosts.map { |h| h.to_s }
171
+ end
172
+ end
173
+
174
+ def test_execute_on_servers_should_require_a_block
175
+ assert_raises(ArgumentError) { @config.execute_on_servers }
176
+ end
177
+
178
+ def test_execute_on_servers_without_current_task_should_call_find_servers
179
+ list = [server("first"), server("second")]
180
+ @config.expects(:find_servers).with(:a => :b, :c => :d).returns(list)
181
+ @config.expects(:establish_connections_to).with(list).returns(:done)
182
+ @config.execute_on_servers(:a => :b, :c => :d) do |result|
183
+ assert_equal list, result
184
+ end
185
+ end
186
+
187
+ def test_execute_on_servers_without_current_task_should_raise_error_if_no_matching_servers
188
+ @config.expects(:find_servers).with(:a => :b, :c => :d).returns([])
189
+ assert_raises(Capistrano::NoMatchingServersError) { @config.execute_on_servers(:a => :b, :c => :d) { |list| } }
190
+ end
191
+
192
+ def test_execute_on_servers_without_current_task_should_not_raise_error_if_no_matching_servers_and_continue_on_no_matching_servers
193
+ @config.expects(:find_servers).with(:a => :b, :c => :d, :on_no_matching_servers => :continue).returns([])
194
+ assert_nothing_raised { @config.execute_on_servers(:a => :b, :c => :d, :on_no_matching_servers => :continue) { |list| } }
195
+ end
196
+
197
+ def test_execute_on_servers_should_raise_an_error_if_the_current_task_has_no_matching_servers_by_default
198
+ @config.current_task = mock_task
199
+ @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns([])
200
+ assert_raises(Capistrano::NoMatchingServersError) do
201
+ @config.execute_on_servers do
202
+ flunk "should not get here"
203
+ end
204
+ end
205
+ end
206
+
207
+ def test_execute_on_servers_should_not_raise_an_error_if_the_current_task_has_no_matching_servers_by_default_and_continue_on_no_matching_servers
208
+ @config.current_task = mock_task(:on_no_matching_servers => :continue)
209
+ @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns([])
210
+ assert_nothing_raised do
211
+ @config.execute_on_servers do
212
+ flunk "should not get here"
213
+ end
214
+ end
215
+ end
216
+
217
+ def test_execute_on_servers_should_not_raise_an_error_if_the_current_task_has_no_matching_servers_by_default_and_command_continues_on_no_matching_servers
218
+ @config.current_task = mock_task
219
+ @config.expects(:find_servers_for_task).with(@config.current_task, :on_no_matching_servers => :continue).returns([])
220
+ assert_nothing_raised do
221
+ @config.execute_on_servers(:on_no_matching_servers => :continue) do
222
+ flunk "should not get here"
223
+ end
224
+ end
225
+ end
226
+
227
+ def test_execute_on_servers_should_determine_server_list_from_active_task
228
+ assert @config.sessions.empty?
229
+ @config.current_task = mock_task
230
+ @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns([server("cap1"), server("cap2"), server("cap3")])
231
+ Capistrano::SSH.expects(:connect).times(3).returns(:success)
232
+ @config.execute_on_servers {}
233
+ assert_equal %w(cap1 cap2 cap3), @config.sessions.keys.sort.map { |s| s.host }
234
+ end
235
+
236
+ def test_execute_on_servers_should_yield_server_list_to_block
237
+ assert @config.sessions.empty?
238
+ @config.current_task = mock_task
239
+ @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns([server("cap1"), server("cap2"), server("cap3")])
240
+ Capistrano::SSH.expects(:connect).times(3).returns(:success)
241
+ block_called = false
242
+ @config.execute_on_servers do |servers|
243
+ block_called = true
244
+ assert servers.detect { |s| s.host == "cap1" }
245
+ assert servers.detect { |s| s.host == "cap2" }
246
+ assert servers.detect { |s| s.host == "cap3" }
247
+ assert servers.all? { |s| @config.sessions[s] }
248
+ end
249
+ assert block_called
250
+ end
251
+
252
+ def test_execute_on_servers_with_once_option_should_establish_connection_to_and_yield_only_the_first_server
253
+ assert @config.sessions.empty?
254
+ @config.current_task = mock_task
255
+ @config.expects(:find_servers_for_task).with(@config.current_task, :once => true).returns([server("cap1"), server("cap2"), server("cap3")])
256
+ Capistrano::SSH.expects(:connect).returns(:success)
257
+ block_called = false
258
+ @config.execute_on_servers(:once => true) do |servers|
259
+ block_called = true
260
+ assert_equal %w(cap1), servers.map { |s| s.host }
261
+ end
262
+ assert block_called
263
+ assert_equal %w(cap1), @config.sessions.keys.sort.map { |s| s.host }
264
+ end
265
+
266
+ def test_execute_servers_should_raise_connection_error_on_failure_by_default
267
+ @config.current_task = mock_task
268
+ @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns([server("cap1")])
269
+ Capistrano::SSH.expects(:connect).raises(Exception)
270
+ assert_raises(Capistrano::ConnectionError) do
271
+ @config.execute_on_servers do
272
+ flunk "expected an exception to be raised"
273
+ end
274
+ end
275
+ end
276
+
277
+ def test_execute_servers_should_not_raise_connection_error_on_failure_with_on_errors_continue
278
+ @config.current_task = mock_task(:on_error => :continue)
279
+ @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns([server("cap1"), server("cap2")])
280
+ Capistrano::SSH.expects(:connect).with(server('cap1'), anything).raises(Exception)
281
+ Capistrano::SSH.expects(:connect).with(server('cap2'), anything).returns(:success)
282
+ assert_nothing_raised {
283
+ @config.execute_on_servers do |servers|
284
+ assert_equal %w(cap2), servers.map { |s| s.host }
285
+ end
286
+ }
287
+ end
288
+
289
+ def test_execute_on_servers_should_not_try_to_connect_to_hosts_with_connection_errors_with_on_errors_continue
290
+ list = [server("cap1"), server("cap2")]
291
+ @config.current_task = mock_task(:on_error => :continue)
292
+ @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns(list)
293
+ Capistrano::SSH.expects(:connect).with(server('cap1'), anything).raises(Exception)
294
+ Capistrano::SSH.expects(:connect).with(server('cap2'), anything).returns(:success)
295
+ @config.execute_on_servers do |servers|
296
+ assert_equal %w(cap2), servers.map { |s| s.host }
297
+ end
298
+ @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns(list)
299
+ @config.execute_on_servers do |servers|
300
+ assert_equal %w(cap2), servers.map { |s| s.host }
301
+ end
302
+ end
303
+
304
+ def test_execute_on_servers_should_not_try_to_connect_to_hosts_with_command_errors_with_on_errors_continue
305
+ cap1 = server("cap1")
306
+ cap2 = server("cap2")
307
+ @config.current_task = mock_task(:on_error => :continue)
308
+ @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns([cap1, cap2])
309
+ Capistrano::SSH.expects(:connect).times(2).returns(:success)
310
+ @config.execute_on_servers do |servers|
311
+ error = Capistrano::CommandError.new
312
+ error.hosts = [cap1]
313
+ raise error
314
+ end
315
+ @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns([cap1, cap2])
316
+ @config.execute_on_servers do |servers|
317
+ assert_equal %w(cap2), servers.map { |s| s.host }
318
+ end
319
+ end
320
+
321
+ def test_execute_on_servers_should_not_try_to_connect_to_hosts_with_transfer_errors_with_on_errors_continue
322
+ cap1 = server("cap1")
323
+ cap2 = server("cap2")
324
+ @config.current_task = mock_task(:on_error => :continue)
325
+ @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns([cap1, cap2])
326
+ Capistrano::SSH.expects(:connect).times(2).returns(:success)
327
+ @config.execute_on_servers do |servers|
328
+ error = Capistrano::TransferError.new
329
+ error.hosts = [cap1]
330
+ raise error
331
+ end
332
+ @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns([cap1, cap2])
333
+ @config.execute_on_servers do |servers|
334
+ assert_equal %w(cap2), servers.map { |s| s.host }
335
+ end
336
+ end
337
+
338
+ def test_connect_should_establish_connections_to_all_servers_in_scope
339
+ assert @config.sessions.empty?
340
+ @config.current_task = mock_task
341
+ @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns([server("cap1"), server("cap2"), server("cap3")])
342
+ Capistrano::SSH.expects(:connect).times(3).returns(:success)
343
+ @config.connect!
344
+ assert_equal %w(cap1 cap2 cap3), @config.sessions.keys.sort.map { |s| s.host }
345
+ end
346
+
347
+ def test_execute_on_servers_should_only_run_on_tasks_max_hosts_hosts_at_once
348
+ cap1 = server("cap1")
349
+ cap2 = server("cap2")
350
+ connection1 = mock()
351
+ connection2 = mock()
352
+ connection1.expects(:close)
353
+ connection2.expects(:close)
354
+ @config.current_task = mock_task(:max_hosts => 1)
355
+ @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns([cap1, cap2])
356
+ Capistrano::SSH.expects(:connect).times(2).returns(connection1).then.returns(connection2)
357
+ block_called = 0
358
+ @config.execute_on_servers do |servers|
359
+ block_called += 1
360
+ assert_equal 1, servers.size
361
+ end
362
+ assert_equal 2, block_called
363
+ end
364
+
365
+ def test_execute_on_servers_should_only_run_on_max_hosts_hosts_at_once
366
+ cap1 = server("cap1")
367
+ cap2 = server("cap2")
368
+ connection1 = mock()
369
+ connection2 = mock()
370
+ connection1.expects(:close)
371
+ connection2.expects(:close)
372
+ @config.current_task = mock_task(:max_hosts => 1)
373
+ @config.expects(:find_servers_for_task).with(@config.current_task, {}).returns([cap1, cap2])
374
+ Capistrano::SSH.expects(:connect).times(2).returns(connection1).then.returns(connection2)
375
+ block_called = 0
376
+ @config.execute_on_servers do |servers|
377
+ block_called += 1
378
+ assert_equal 1, servers.size
379
+ end
380
+ assert_equal 2, block_called
381
+ end
382
+
383
+ def test_execute_on_servers_should_cope_with_already_dropped_connections_when_attempting_to_close_them
384
+ cap1 = server("cap1")
385
+ cap2 = server("cap2")
386
+ connection1 = mock()
387
+ connection2 = mock()
388
+ connection3 = mock()
389
+ connection4 = mock()
390
+ connection1.expects(:close).raises(IOError)
391
+ connection2.expects(:close)
392
+ connection3.expects(:close)
393
+ connection4.expects(:close)
394
+ @config.current_task = mock_task(:max_hosts => 1)
395
+ @config.expects(:find_servers_for_task).times(2).with(@config.current_task, {}).returns([cap1, cap2])
396
+ Capistrano::SSH.expects(:connect).times(4).returns(connection1).then.returns(connection2).then.returns(connection3).then.returns(connection4)
397
+ @config.execute_on_servers {}
398
+ @config.execute_on_servers {}
399
+ end
400
+
401
+ def test_execute_on_servers_should_cope_with_already_disconnected_connections_when_attempting_to_close_them
402
+ cap1 = server("cap1")
403
+ cap2 = server("cap2")
404
+ connection1 = mock()
405
+ connection2 = mock()
406
+ connection3 = mock()
407
+ connection4 = mock()
408
+ connection1.expects(:close).raises(Net::SSH::Disconnect)
409
+ connection2.expects(:close)
410
+ connection3.expects(:close)
411
+ connection4.expects(:close)
412
+ @config.current_task = mock_task(:max_hosts => 1)
413
+ @config.expects(:find_servers_for_task).times(2).with(@config.current_task, {}).returns([cap1, cap2])
414
+ Capistrano::SSH.expects(:connect).times(4).returns(connection1).then.returns(connection2).then.returns(connection3).then.returns(connection4)
415
+ @config.execute_on_servers {}
416
+ @config.execute_on_servers {}
417
+ end
418
+
419
+ def test_connect_should_honor_once_option
420
+ assert @config.sessions.empty?
421
+ @config.current_task = mock_task
422
+ @config.expects(:find_servers_for_task).with(@config.current_task, :once => true).returns([server("cap1"), server("cap2"), server("cap3")])
423
+ Capistrano::SSH.expects(:connect).returns(:success)
424
+ @config.connect! :once => true
425
+ assert_equal %w(cap1), @config.sessions.keys.sort.map { |s| s.host }
426
+ end
427
+
428
+ private
429
+
430
+ def mock_task(options={})
431
+ continue_on_error = options[:on_error] == :continue
432
+ stub("task",
433
+ :fully_qualified_name => "name",
434
+ :options => options,
435
+ :continue_on_error? => continue_on_error,
436
+ :max_hosts => options[:max_hosts]
437
+ )
438
+ end
439
+ end
@@ -0,0 +1,175 @@
1
+ require "utils"
2
+ require 'capistrano/configuration/execution'
3
+ require 'capistrano/task_definition'
4
+
5
+ class ConfigurationExecutionTest < Test::Unit::TestCase
6
+ class MockConfig
7
+ attr_reader :tasks, :namespaces, :fully_qualified_name, :parent
8
+ attr_reader :state, :original_initialize_called
9
+ attr_accessor :logger, :default_task
10
+
11
+ def initialize(options={})
12
+ @original_initialize_called = true
13
+ @tasks = {}
14
+ @namespaces = {}
15
+ @state = {}
16
+ @fully_qualified_name = options[:fqn]
17
+ @parent = options[:parent]
18
+ @logger = options.delete(:logger)
19
+ end
20
+
21
+ include Capistrano::Configuration::Execution
22
+ end
23
+
24
+ def setup
25
+ @config = MockConfig.new(:logger => stub(:debug => nil, :info => nil, :important => nil))
26
+ @config.stubs(:search_task).returns(nil)
27
+ end
28
+
29
+ def test_initialize_should_initialize_collections
30
+ assert_nil @config.rollback_requests
31
+ assert @config.original_initialize_called
32
+ assert @config.task_call_frames.empty?
33
+ end
34
+
35
+ def test_execute_task_should_populate_call_stack
36
+ task = new_task @config, :testing
37
+ assert_nothing_raised { @config.execute_task(task) }
38
+ assert_equal %w(testing), @config.state[:testing][:stack]
39
+ assert_nil @config.state[:testing][:history]
40
+ assert @config.task_call_frames.empty?
41
+ end
42
+
43
+ def test_nested_execute_task_should_add_to_call_stack
44
+ testing = new_task @config, :testing
45
+ outer = new_task(@config, :outer) { execute_task(testing) }
46
+
47
+ assert_nothing_raised { @config.execute_task(outer) }
48
+ assert_equal %w(outer testing), @config.state[:testing][:stack]
49
+ assert_nil @config.state[:testing][:history]
50
+ assert @config.task_call_frames.empty?
51
+ end
52
+
53
+ def test_execute_task_should_execute_in_scope_of_tasks_parent
54
+ ns = stub("namespace", :tasks => {}, :default_task => nil, :fully_qualified_name => "ns")
55
+ ns.expects(:instance_eval)
56
+ testing = new_task ns, :testing
57
+ @config.execute_task(testing)
58
+ end
59
+
60
+ def test_transaction_outside_of_task_should_raise_exception
61
+ assert_raises(ScriptError) { @config.transaction {} }
62
+ end
63
+
64
+ def test_transaction_without_block_should_raise_argument_error
65
+ testing = new_task(@config, :testing) { transaction }
66
+ assert_raises(ArgumentError) { @config.execute_task(testing) }
67
+ end
68
+
69
+ def test_transaction_should_initialize_transaction_history
70
+ @config.state[:inspector] = stack_inspector
71
+ testing = new_task(@config, :testing) { transaction { instance_eval(&state[:inspector]) } }
72
+ @config.execute_task(testing)
73
+ assert_equal [], @config.state[:testing][:history]
74
+ end
75
+
76
+ def test_transaction_from_within_transaction_should_not_start_new_transaction
77
+ third = new_task(@config, :third, &stack_inspector)
78
+ second = new_task(@config, :second) { transaction { execute_task(third) } }
79
+ first = new_task(@config, :first) { transaction { execute_task(second) } }
80
+ # kind of fragile...not sure how else to check that transaction was only
81
+ # really run twice...but if the transaction was REALLY run, logger.info
82
+ # will be called once when it starts, and once when it finishes.
83
+ @config.logger = mock()
84
+ @config.logger.stubs(:debug)
85
+ @config.logger.expects(:info).times(2)
86
+ @config.execute_task(first)
87
+ end
88
+
89
+ def test_on_rollback_should_have_no_effect_outside_of_transaction
90
+ aaa = new_task(@config, :aaa) { on_rollback { state[:rollback] = true }; raise "boom" }
91
+ assert_raises(RuntimeError) { @config.execute_task(aaa) }
92
+ assert_nil @config.state[:rollback]
93
+ end
94
+
95
+ def test_exception_raised_in_transaction_should_call_all_registered_rollback_handlers_in_reverse_order
96
+ aaa = new_task(@config, :aaa) { on_rollback { (state[:rollback] ||= []) << :aaa } }
97
+ bbb = new_task(@config, :bbb) { on_rollback { (state[:rollback] ||= []) << :bbb } }
98
+ ccc = new_task(@config, :ccc) {}
99
+ ddd = new_task(@config, :ddd) { on_rollback { (state[:rollback] ||= []) << :ddd }; execute_task(bbb); execute_task(ccc) }
100
+ eee = new_task(@config, :eee) { transaction { execute_task(ddd); execute_task(aaa); raise "boom" } }
101
+ assert_raises(RuntimeError) do
102
+ @config.execute_task(eee)
103
+ end
104
+ assert_equal [:aaa, :bbb, :ddd], @config.state[:rollback]
105
+ assert_nil @config.rollback_requests
106
+ assert @config.task_call_frames.empty?
107
+ end
108
+
109
+ def test_exception_during_rollback_should_simply_be_logged_and_ignored
110
+ aaa = new_task(@config, :aaa) { on_rollback { state[:aaa] = true; raise LoadError, "ouch" }; execute_task(bbb) }
111
+ bbb = new_task(@config, :bbb) { raise MadError, "boom" }
112
+ ccc = new_task(@config, :ccc) { transaction { execute_task(aaa) } }
113
+ assert_raises(NameError) do
114
+ @config.execute_task(ccc)
115
+ end
116
+ assert @config.state[:aaa]
117
+ end
118
+
119
+ def test_on_rollback_called_twice_should_result_in_last_rollback_block_being_effective
120
+ aaa = new_task(@config, :aaa) do
121
+ transaction do
122
+ on_rollback { (state[:rollback] ||= []) << :first }
123
+ on_rollback { (state[:rollback] ||= []) << :second }
124
+ raise "boom"
125
+ end
126
+ end
127
+
128
+ assert_raises(RuntimeError) do
129
+ @config.execute_task(aaa)
130
+ end
131
+
132
+ assert_equal [:second], @config.state[:rollback]
133
+ end
134
+
135
+ def test_find_and_execute_task_should_raise_error_when_task_cannot_be_found
136
+ @config.expects(:find_task).with("path:to:task").returns(nil)
137
+ assert_raises(Capistrano::NoSuchTaskError) { @config.find_and_execute_task("path:to:task") }
138
+ end
139
+
140
+ def test_find_and_execute_task_should_execute_task_when_task_is_found
141
+ @config.expects(:find_task).with("path:to:task").returns(:found)
142
+ @config.expects(:execute_task).with(:found)
143
+ assert_nothing_raised { @config.find_and_execute_task("path:to:task") }
144
+ end
145
+
146
+ def test_find_and_execute_task_with_before_option_should_trigger_callback
147
+ @config.expects(:find_task).with("path:to:task").returns(:found)
148
+ @config.expects(:trigger).with(:incoming, :found)
149
+ @config.expects(:execute_task).with(:found)
150
+ @config.find_and_execute_task("path:to:task", :before => :incoming)
151
+ end
152
+
153
+ def test_find_and_execute_task_with_after_option_should_trigger_callback
154
+ @config.expects(:find_task).with("path:to:task").returns(:found)
155
+ @config.expects(:trigger).with(:outgoing, :found)
156
+ @config.expects(:execute_task).with(:found)
157
+ @config.find_and_execute_task("path:to:task", :after => :outgoing)
158
+ end
159
+
160
+ private
161
+
162
+ def stack_inspector
163
+ Proc.new do
164
+ (state[:trail] ||= []) << current_task.fully_qualified_name
165
+ data = state[current_task.name] = {}
166
+ data[:stack] = task_call_frames.map { |frame| frame.task.fully_qualified_name }
167
+ data[:history] = rollback_requests && rollback_requests.map { |frame| frame.task.fully_qualified_name }
168
+ end
169
+ end
170
+
171
+ def new_task(namespace, name, options={}, &block)
172
+ block ||= stack_inspector
173
+ namespace.tasks[name] = Capistrano::TaskDefinition.new(name, namespace, &block)
174
+ end
175
+ end