capistrano 1.4.2 → 2.0.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/CHANGELOG +140 -4
- data/MIT-LICENSE +1 -1
- data/README +22 -14
- data/bin/cap +1 -8
- data/bin/capify +77 -0
- data/examples/sample.rb +10 -109
- data/lib/capistrano.rb +1 -0
- data/lib/capistrano/callback.rb +41 -0
- data/lib/capistrano/cli.rb +17 -317
- data/lib/capistrano/cli/execute.rb +82 -0
- data/lib/capistrano/cli/help.rb +102 -0
- data/lib/capistrano/cli/help.txt +53 -0
- data/lib/capistrano/cli/options.rb +183 -0
- data/lib/capistrano/cli/ui.rb +28 -0
- data/lib/capistrano/command.rb +62 -29
- data/lib/capistrano/configuration.rb +25 -226
- data/lib/capistrano/configuration/actions/file_transfer.rb +35 -0
- data/lib/capistrano/configuration/actions/inspect.rb +46 -0
- data/lib/capistrano/configuration/actions/invocation.rb +127 -0
- data/lib/capistrano/configuration/callbacks.rb +148 -0
- data/lib/capistrano/configuration/connections.rb +159 -0
- data/lib/capistrano/configuration/execution.rb +126 -0
- data/lib/capistrano/configuration/loading.rb +112 -0
- data/lib/capistrano/configuration/namespaces.rb +190 -0
- data/lib/capistrano/configuration/roles.rb +51 -0
- data/lib/capistrano/configuration/servers.rb +75 -0
- data/lib/capistrano/configuration/variables.rb +127 -0
- data/lib/capistrano/errors.rb +15 -0
- data/lib/capistrano/extensions.rb +27 -8
- data/lib/capistrano/gateway.rb +54 -29
- data/lib/capistrano/logger.rb +11 -11
- data/lib/capistrano/recipes/compat.rb +32 -0
- data/lib/capistrano/recipes/deploy.rb +483 -0
- data/lib/capistrano/recipes/deploy/dependencies.rb +44 -0
- data/lib/capistrano/recipes/deploy/local_dependency.rb +46 -0
- data/lib/capistrano/recipes/deploy/remote_dependency.rb +65 -0
- data/lib/capistrano/recipes/deploy/scm.rb +19 -0
- data/lib/capistrano/recipes/deploy/scm/base.rb +180 -0
- data/lib/capistrano/recipes/deploy/scm/bzr.rb +86 -0
- data/lib/capistrano/recipes/deploy/scm/cvs.rb +151 -0
- data/lib/capistrano/recipes/deploy/scm/darcs.rb +85 -0
- data/lib/capistrano/recipes/deploy/scm/mercurial.rb +129 -0
- data/lib/capistrano/recipes/deploy/scm/perforce.rb +126 -0
- data/lib/capistrano/recipes/deploy/scm/subversion.rb +103 -0
- data/lib/capistrano/recipes/deploy/strategy.rb +19 -0
- data/lib/capistrano/recipes/deploy/strategy/base.rb +64 -0
- data/lib/capistrano/recipes/deploy/strategy/checkout.rb +20 -0
- data/lib/capistrano/recipes/deploy/strategy/copy.rb +143 -0
- data/lib/capistrano/recipes/deploy/strategy/export.rb +20 -0
- data/lib/capistrano/recipes/deploy/strategy/remote.rb +52 -0
- data/lib/capistrano/recipes/deploy/strategy/remote_cache.rb +47 -0
- data/lib/capistrano/recipes/deploy/templates/maintenance.rhtml +53 -0
- data/lib/capistrano/recipes/standard.rb +26 -276
- data/lib/capistrano/recipes/templates/maintenance.rhtml +1 -1
- data/lib/capistrano/recipes/upgrade.rb +33 -0
- data/lib/capistrano/server_definition.rb +51 -0
- data/lib/capistrano/shell.rb +125 -81
- data/lib/capistrano/ssh.rb +80 -36
- data/lib/capistrano/task_definition.rb +69 -0
- data/lib/capistrano/upload.rb +146 -0
- data/lib/capistrano/version.rb +13 -17
- data/test/cli/execute_test.rb +132 -0
- data/test/cli/help_test.rb +139 -0
- data/test/cli/options_test.rb +226 -0
- data/test/cli/ui_test.rb +28 -0
- data/test/cli_test.rb +17 -0
- data/test/command_test.rb +284 -25
- data/test/configuration/actions/file_transfer_test.rb +40 -0
- data/test/configuration/actions/inspect_test.rb +62 -0
- data/test/configuration/actions/invocation_test.rb +195 -0
- data/test/configuration/callbacks_test.rb +206 -0
- data/test/configuration/connections_test.rb +288 -0
- data/test/configuration/execution_test.rb +159 -0
- data/test/configuration/loading_test.rb +119 -0
- data/test/configuration/namespace_dsl_test.rb +283 -0
- data/test/configuration/roles_test.rb +47 -0
- data/test/configuration/servers_test.rb +90 -0
- data/test/configuration/variables_test.rb +180 -0
- data/test/configuration_test.rb +60 -212
- data/test/deploy/scm/base_test.rb +55 -0
- data/test/deploy/strategy/copy_test.rb +146 -0
- data/test/extensions_test.rb +69 -0
- data/test/fixtures/cli_integration.rb +5 -0
- data/test/fixtures/custom.rb +2 -2
- data/test/gateway_test.rb +167 -0
- data/test/logger_test.rb +123 -0
- data/test/server_definition_test.rb +108 -0
- data/test/shell_test.rb +64 -0
- data/test/ssh_test.rb +67 -154
- data/test/task_definition_test.rb +101 -0
- data/test/upload_test.rb +131 -0
- data/test/utils.rb +31 -39
- data/test/version_test.rb +24 -0
- metadata +145 -98
- data/THANKS +0 -4
- data/lib/capistrano/actor.rb +0 -567
- data/lib/capistrano/generators/rails/deployment/deployment_generator.rb +0 -25
- data/lib/capistrano/generators/rails/deployment/templates/capistrano.rake +0 -49
- data/lib/capistrano/generators/rails/deployment/templates/deploy.rb +0 -122
- data/lib/capistrano/generators/rails/loader.rb +0 -20
- data/lib/capistrano/scm/base.rb +0 -61
- data/lib/capistrano/scm/baz.rb +0 -118
- data/lib/capistrano/scm/bzr.rb +0 -70
- data/lib/capistrano/scm/cvs.rb +0 -129
- data/lib/capistrano/scm/darcs.rb +0 -27
- data/lib/capistrano/scm/mercurial.rb +0 -83
- data/lib/capistrano/scm/perforce.rb +0 -139
- data/lib/capistrano/scm/subversion.rb +0 -128
- data/lib/capistrano/transfer.rb +0 -97
- data/lib/capistrano/utils.rb +0 -26
- data/test/actor_test.rb +0 -402
- data/test/scm/cvs_test.rb +0 -196
- data/test/scm/subversion_test.rb +0 -145
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'capistrano/server_definition'
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
# Represents the definition of a single task.
|
5
|
+
class TaskDefinition
|
6
|
+
attr_reader :name, :namespace, :options, :body, :desc, :on_error
|
7
|
+
|
8
|
+
def initialize(name, namespace, options={}, &block)
|
9
|
+
@name, @namespace, @options = name, namespace, options
|
10
|
+
@desc = @options.delete(:desc)
|
11
|
+
@on_error = options.delete(:on_error)
|
12
|
+
@body = block or raise ArgumentError, "a task requires a block"
|
13
|
+
@servers = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns the task's fully-qualified name, including the namespace
|
17
|
+
def fully_qualified_name
|
18
|
+
@fully_qualified_name ||= begin
|
19
|
+
if namespace.default_task == self
|
20
|
+
namespace.fully_qualified_name
|
21
|
+
else
|
22
|
+
[namespace.fully_qualified_name, name].compact.join(":")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns the description for this task, with newlines collapsed and
|
28
|
+
# whitespace stripped. Returns the empty string if there is no
|
29
|
+
# description for this task.
|
30
|
+
def description(rebuild=false)
|
31
|
+
@description = nil if rebuild
|
32
|
+
@description ||= begin
|
33
|
+
description = @desc || ""
|
34
|
+
|
35
|
+
indentation = description[/\A\s+/]
|
36
|
+
if indentation
|
37
|
+
reformatted_description = ""
|
38
|
+
description.strip.each_line do |line|
|
39
|
+
line = line.chomp.sub(/^#{indentation}/, "")
|
40
|
+
line = line.gsub(/#{indentation}\s*/, " ") if line[/^\S/]
|
41
|
+
reformatted_description << line << "\n"
|
42
|
+
end
|
43
|
+
description = reformatted_description
|
44
|
+
end
|
45
|
+
|
46
|
+
description.strip.gsub(/\r\n/, "\n")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns the first sentence of the full description. If +max_length+ is
|
51
|
+
# given, the result will be truncated if it is longer than +max_length+,
|
52
|
+
# and an ellipsis appended.
|
53
|
+
def brief_description(max_length=nil)
|
54
|
+
brief = description[/^.*?\.(?=\s|$)/] || description
|
55
|
+
|
56
|
+
if max_length && brief.length > max_length
|
57
|
+
brief = brief[0,max_length-3] + "..."
|
58
|
+
end
|
59
|
+
|
60
|
+
brief
|
61
|
+
end
|
62
|
+
|
63
|
+
# Indicates whether the task wants to continue, even if a server has failed
|
64
|
+
# previously
|
65
|
+
def continue_on_error?
|
66
|
+
@on_error == :continue
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'net/sftp'
|
2
|
+
require 'net/sftp/operations/errors'
|
3
|
+
require 'capistrano/errors'
|
4
|
+
|
5
|
+
module Capistrano
|
6
|
+
unless ENV['SKIP_VERSION_CHECK']
|
7
|
+
require 'capistrano/version'
|
8
|
+
require 'net/sftp/version'
|
9
|
+
sftp_version = [Net::SFTP::Version::MAJOR, Net::SFTP::Version::MINOR, Net::SFTP::Version::TINY]
|
10
|
+
required_version = [1,1,0]
|
11
|
+
if !Capistrano::Version.check(required_version, sftp_version)
|
12
|
+
raise "You have Net::SFTP #{sftp_version.join(".")}, but you need at least #{required_version.join(".")}. Net::SFTP will not be used."
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# This class encapsulates a single file upload to be performed in parallel
|
17
|
+
# across multiple machines, using the SFTP protocol. Although it is intended
|
18
|
+
# to be used primarily from within Capistrano, it may also be used standalone
|
19
|
+
# if you need to simply upload a file to multiple servers.
|
20
|
+
#
|
21
|
+
# Basic Usage:
|
22
|
+
#
|
23
|
+
# begin
|
24
|
+
# uploader = Capistrano::Upload.new(sessions, "remote-file.txt",
|
25
|
+
# :data => "the contents of the file to upload")
|
26
|
+
# uploader.process!
|
27
|
+
# rescue Capistrano::UploadError => e
|
28
|
+
# warn "Could not upload the file: #{e.message}"
|
29
|
+
# end
|
30
|
+
class Upload
|
31
|
+
def self.process(sessions, filename, options)
|
32
|
+
new(sessions, filename, options).process!
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :sessions, :filename, :options
|
36
|
+
attr_reader :failed, :completed
|
37
|
+
|
38
|
+
# Creates and prepares a new Upload instance. The +sessions+ parameter
|
39
|
+
# must be an array of open Net::SSH sessions. The +filename+ is the name
|
40
|
+
# (including path) of the destination file on the remote server. The
|
41
|
+
# +options+ hash accepts the following keys (as symbols):
|
42
|
+
#
|
43
|
+
# * data: required. Should refer to a String containing the contents of
|
44
|
+
# the file to upload.
|
45
|
+
# * mode: optional. The "mode" of the destination file. Defaults to 0660.
|
46
|
+
# * logger: optional. Should point to a Capistrano::Logger instance, if
|
47
|
+
# given.
|
48
|
+
def initialize(sessions, filename, options)
|
49
|
+
raise ArgumentError, "you must specify the data to upload via the :data option" unless options[:data]
|
50
|
+
|
51
|
+
@sessions = sessions
|
52
|
+
@filename = filename
|
53
|
+
@options = options
|
54
|
+
|
55
|
+
@completed = @failed = 0
|
56
|
+
@sftps = setup_sftp
|
57
|
+
end
|
58
|
+
|
59
|
+
# Uploads to all specified servers in parallel. If any one of the servers
|
60
|
+
# fails, an exception will be raised (UploadError).
|
61
|
+
def process!
|
62
|
+
logger.debug "uploading #{filename}" if logger
|
63
|
+
while running?
|
64
|
+
@sftps.each do |sftp|
|
65
|
+
next if sftp.channel[:done]
|
66
|
+
begin
|
67
|
+
sftp.channel.connection.process(true)
|
68
|
+
rescue Net::SFTP::Operations::StatusException => error
|
69
|
+
logger.important "uploading failed: #{error.description}", sftp.channel[:server] if logger
|
70
|
+
failed!(sftp)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
sleep 0.01 # a brief respite, to keep the CPU from going crazy
|
74
|
+
end
|
75
|
+
logger.trace "upload finished" if logger
|
76
|
+
|
77
|
+
if (failed = @sftps.select { |sftp| sftp.channel[:failed] }).any?
|
78
|
+
hosts = failed.map { |sftp| sftp.channel[:server] }
|
79
|
+
error = UploadError.new("upload of #{filename} failed on #{hosts.join(',')}")
|
80
|
+
error.hosts = hosts
|
81
|
+
raise error
|
82
|
+
end
|
83
|
+
|
84
|
+
self
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def logger
|
90
|
+
options[:logger]
|
91
|
+
end
|
92
|
+
|
93
|
+
def setup_sftp
|
94
|
+
sessions.map do |session|
|
95
|
+
server = session.xserver
|
96
|
+
sftp = session.sftp
|
97
|
+
sftp.connect unless sftp.state == :open
|
98
|
+
|
99
|
+
sftp.channel[:server] = server
|
100
|
+
sftp.channel[:done] = false
|
101
|
+
sftp.channel[:failed] = false
|
102
|
+
|
103
|
+
real_filename = filename.gsub(/\$CAPISTRANO:HOST\$/, server.host)
|
104
|
+
sftp.open(real_filename, IO::WRONLY | IO::CREAT | IO::TRUNC, options[:mode] || 0660) do |status, handle|
|
105
|
+
break unless check_status(sftp, "open #{real_filename}", server, status)
|
106
|
+
|
107
|
+
logger.info "uploading data to #{server}:#{real_filename}" if logger
|
108
|
+
sftp.write(handle, options[:data] || "") do |status|
|
109
|
+
break unless check_status(sftp, "write to #{server}:#{real_filename}", server, status)
|
110
|
+
sftp.close_handle(handle) do
|
111
|
+
logger.debug "done uploading data to #{server}:#{real_filename}" if logger
|
112
|
+
completed!(sftp)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
sftp
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def check_status(sftp, action, server, status)
|
122
|
+
return true if status.code == Net::SFTP::Session::FX_OK
|
123
|
+
|
124
|
+
logger.error "could not #{action} on #{server} (#{status.message})" if logger
|
125
|
+
failed!(sftp)
|
126
|
+
|
127
|
+
return false
|
128
|
+
end
|
129
|
+
|
130
|
+
def running?
|
131
|
+
completed < @sftps.length
|
132
|
+
end
|
133
|
+
|
134
|
+
def failed!(sftp)
|
135
|
+
completed!(sftp)
|
136
|
+
@failed += 1
|
137
|
+
sftp.channel[:failed] = true
|
138
|
+
end
|
139
|
+
|
140
|
+
def completed!(sftp)
|
141
|
+
@completed += 1
|
142
|
+
sftp.channel[:done] = true
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
data/lib/capistrano/version.rb
CHANGED
@@ -1,26 +1,22 @@
|
|
1
1
|
module Capistrano
|
2
2
|
module Version #:nodoc:
|
3
|
-
# A method for comparing versions of required modules. It expects
|
4
|
-
# arrays as parameters,
|
5
|
-
#
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
return actual >= minimum && actual < maximum
|
3
|
+
# A method for comparing versions of required modules. It expects two
|
4
|
+
# arrays of integers as parameters, the first being the minimum version
|
5
|
+
# required, and the second being the actual version available. It returns
|
6
|
+
# true if the actual version is at least equal to the required version.
|
7
|
+
def self.check(required, actual) #:nodoc:
|
8
|
+
required = required.map { |v| "%06d" % v }.join(".")
|
9
|
+
actual = actual.map { |v| "%06d" % v }.join(".")
|
10
|
+
return actual >= required
|
12
11
|
end
|
13
12
|
|
14
|
-
MAJOR =
|
15
|
-
MINOR =
|
16
|
-
TINY =
|
13
|
+
MAJOR = 2
|
14
|
+
MINOR = 0
|
15
|
+
TINY = 0
|
17
16
|
|
18
17
|
STRING = [MAJOR, MINOR, TINY].join(".")
|
19
18
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
MINIMUM_SFTP_REQUIRED = [1,1,0]
|
24
|
-
MAXIMUM_SFTP_REQUIRED = [1,99,0]
|
19
|
+
SSH_REQUIRED = [1,0,10]
|
20
|
+
SFTP_REQUIRED = [1,1,0]
|
25
21
|
end
|
26
22
|
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/../utils"
|
2
|
+
require 'capistrano/cli/execute'
|
3
|
+
|
4
|
+
class CLIExecuteTest < Test::Unit::TestCase
|
5
|
+
class MockCLI
|
6
|
+
attr_reader :options
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@options = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
include Capistrano::CLI::Execute
|
13
|
+
end
|
14
|
+
|
15
|
+
def setup
|
16
|
+
@cli = MockCLI.new
|
17
|
+
@logger = stub_everything
|
18
|
+
@config = stub(:logger => @logger)
|
19
|
+
@config.stubs(:set)
|
20
|
+
@config.stubs(:load)
|
21
|
+
@config.stubs(:trigger)
|
22
|
+
@cli.stubs(:instantiate_configuration).returns(@config)
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_execute_should_set_logger_verbosity
|
26
|
+
@cli.options[:verbose] = 7
|
27
|
+
@logger.expects(:level=).with(7)
|
28
|
+
@cli.execute!
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_execute_should_set_password
|
32
|
+
@cli.options[:password] = "nosoup4u"
|
33
|
+
@config.expects(:set).with(:password, "nosoup4u")
|
34
|
+
@cli.execute!
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_execute_should_set_prevars_before_loading
|
38
|
+
@config.expects(:load).never
|
39
|
+
@config.expects(:set).with(:stage, "foobar")
|
40
|
+
@config.expects(:load).with("standard")
|
41
|
+
@cli.options[:pre_vars] = { :stage => "foobar" }
|
42
|
+
@cli.execute!
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_execute_should_load_sysconf_if_sysconf_set_and_exists
|
46
|
+
@cli.options[:sysconf] = "/etc/capistrano.conf"
|
47
|
+
@config.expects(:load).with("/etc/capistrano.conf")
|
48
|
+
File.expects(:file?).with("/etc/capistrano.conf").returns(true)
|
49
|
+
@cli.execute!
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_execute_should_not_load_sysconf_when_sysconf_set_and_not_exists
|
53
|
+
@cli.options[:sysconf] = "/etc/capistrano.conf"
|
54
|
+
File.expects(:file?).with("/etc/capistrano.conf").returns(false)
|
55
|
+
@cli.execute!
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_execute_should_load_dotfile_if_dotfile_set_and_exists
|
59
|
+
@cli.options[:dotfile] = "/home/jamis/.caprc"
|
60
|
+
@config.expects(:load).with("/home/jamis/.caprc")
|
61
|
+
File.expects(:file?).with("/home/jamis/.caprc").returns(true)
|
62
|
+
@cli.execute!
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_execute_should_not_load_dotfile_when_dotfile_set_and_not_exists
|
66
|
+
@cli.options[:dotfile] = "/home/jamis/.caprc"
|
67
|
+
File.expects(:file?).with("/home/jamis/.caprc").returns(false)
|
68
|
+
@cli.execute!
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_execute_should_load_recipes_when_recipes_are_given
|
72
|
+
@cli.options[:recipes] = %w(config/deploy path/to/extra)
|
73
|
+
@config.expects(:load).with("config/deploy")
|
74
|
+
@config.expects(:load).with("path/to/extra")
|
75
|
+
@cli.execute!
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_execute_should_set_vars_and_execute_tasks
|
79
|
+
@cli.options[:vars] = { :foo => "bar", :baz => "bang" }
|
80
|
+
@cli.options[:actions] = %w(first second)
|
81
|
+
@config.expects(:set).with(:foo, "bar")
|
82
|
+
@config.expects(:set).with(:baz, "bang")
|
83
|
+
@config.expects(:find_and_execute_task).with("first", :before => :start, :after => :finish)
|
84
|
+
@config.expects(:find_and_execute_task).with("second", :before => :start, :after => :finish)
|
85
|
+
@cli.execute!
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_execute_should_call_load_and_exit_triggers
|
89
|
+
@cli.options[:actions] = %w(first second)
|
90
|
+
@config.expects(:find_and_execute_task).with("first", :before => :start, :after => :finish)
|
91
|
+
@config.expects(:find_and_execute_task).with("second", :before => :start, :after => :finish)
|
92
|
+
@config.expects(:trigger).never
|
93
|
+
@config.expects(:trigger).with(:load)
|
94
|
+
@config.expects(:trigger).with(:exit)
|
95
|
+
@cli.execute!
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_execute_should_call_handle_error_when_exceptions_occur
|
99
|
+
@config.expects(:load).raises(Exception, "boom")
|
100
|
+
@cli.expects(:handle_error).with { |e,| Exception === e }
|
101
|
+
@cli.execute!
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_execute_should_return_config_instance
|
105
|
+
assert_equal @config, @cli.execute!
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_instantiate_configuration_should_return_new_configuration_instance
|
109
|
+
assert_instance_of Capistrano::Configuration, MockCLI.new.instantiate_configuration
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_handle_error_with_auth_error_should_abort_with_message_including_user_name
|
113
|
+
@cli.expects(:abort).with { |s| s.include?("jamis") }
|
114
|
+
@cli.handle_error(Net::SSH::AuthenticationFailed.new("jamis"))
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_handle_error_with_cap_error_should_abort_with_message
|
118
|
+
@cli.expects(:abort).with("Wish you were here")
|
119
|
+
@cli.handle_error(Capistrano::Error.new("Wish you were here"))
|
120
|
+
end
|
121
|
+
|
122
|
+
def test_handle_error_with_other_errors_should_reraise_error
|
123
|
+
other_error = Class.new(RuntimeError)
|
124
|
+
assert_raises(other_error) { @cli.handle_error(other_error.new("boom")) }
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_class_execute_method_should_call_parse_and_execute_with_ARGV
|
128
|
+
cli = mock(:execute! => nil)
|
129
|
+
MockCLI.expects(:parse).with(ARGV).returns(cli)
|
130
|
+
MockCLI.execute
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/../utils"
|
2
|
+
require 'capistrano/cli/help'
|
3
|
+
|
4
|
+
class CLIHelpTest < Test::Unit::TestCase
|
5
|
+
class MockCLI
|
6
|
+
attr_reader :options, :called_original
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@options = {}
|
10
|
+
@called_original = false
|
11
|
+
end
|
12
|
+
|
13
|
+
def execute_requested_actions(config)
|
14
|
+
@called_original = config
|
15
|
+
end
|
16
|
+
|
17
|
+
include Capistrano::CLI::Help
|
18
|
+
end
|
19
|
+
|
20
|
+
def setup
|
21
|
+
@cli = MockCLI.new
|
22
|
+
@cli.options[:verbose] = 0
|
23
|
+
@ui = stub("ui", :output_cols => 80, :output_rows => 20, :page_at= => nil)
|
24
|
+
MockCLI.stubs(:ui).returns(@ui)
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_execute_requested_actions_without_tasks_or_explain_should_call_original
|
28
|
+
@cli.execute_requested_actions(:config)
|
29
|
+
@cli.expects(:task_list).never
|
30
|
+
@cli.expects(:explain_task).never
|
31
|
+
assert_equal :config, @cli.called_original
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_execute_requested_actions_with_tasks_should_call_task_list
|
35
|
+
@cli.options[:tasks] = true
|
36
|
+
@cli.expects(:task_list).with(:config)
|
37
|
+
@cli.expects(:explain_task).never
|
38
|
+
@cli.execute_requested_actions(:config)
|
39
|
+
assert !@cli.called_original
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_execute_requested_actions_with_explain_should_call_explain_task
|
43
|
+
@cli.options[:explain] = "deploy_with_niftiness"
|
44
|
+
@cli.expects(:task_list).never
|
45
|
+
@cli.expects(:explain_task).with(:config, "deploy_with_niftiness")
|
46
|
+
@cli.execute_requested_actions(:config)
|
47
|
+
assert !@cli.called_original
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_task_list_with_no_tasks_should_emit_warning
|
51
|
+
config = mock("config", :task_list => [])
|
52
|
+
@cli.expects(:warn)
|
53
|
+
@cli.task_list(config)
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_task_list_should_query_all_tasks_in_all_namespaces
|
57
|
+
expected_max_len = 80 - 3 - MockCLI::LINE_PADDING
|
58
|
+
task_list = [task("c"), task("g", "c:g"), task("b", "c:b"), task("a")]
|
59
|
+
task_list.each { |t| t.expects(:brief_description).with(expected_max_len).returns(t.fully_qualified_name) }
|
60
|
+
|
61
|
+
config = mock("config")
|
62
|
+
config.expects(:task_list).with(:all).returns(task_list)
|
63
|
+
@cli.stubs(:puts)
|
64
|
+
@cli.task_list(config)
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_task_list_should_never_use_less_than_MIN_MAX_LEN_chars_for_descriptions
|
68
|
+
@ui.stubs(:output_cols).returns(20)
|
69
|
+
t = task("c")
|
70
|
+
t.expects(:brief_description).with(30).returns("hello")
|
71
|
+
config = mock("config", :task_list => [t])
|
72
|
+
@cli.stubs(:puts)
|
73
|
+
@cli.task_list(config)
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_task_list_should_not_include_tasks_with_blank_description_or_internal_by_default
|
77
|
+
t1 = task("c")
|
78
|
+
t1.expects(:brief_description).returns("hello")
|
79
|
+
t2 = task("d", "d", "[internal] howdy")
|
80
|
+
t2.expects(:brief_description).never
|
81
|
+
t3 = task("e", "e", "")
|
82
|
+
t3.expects(:brief_description).never
|
83
|
+
|
84
|
+
config = mock("config", :task_list => [t1, t2, t3])
|
85
|
+
@cli.stubs(:puts)
|
86
|
+
@cli.expects(:puts).never.with { |s,| (s || "").include?("[internal]") || s =~ /#\s*$/ }
|
87
|
+
@cli.task_list(config)
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_task_list_should_include_tasks_with_blank_descriptions_and_internal_when_verbose
|
91
|
+
t1 = task("c")
|
92
|
+
t1.expects(:brief_description).returns("hello")
|
93
|
+
t2 = task("d", "d", "[internal] howdy")
|
94
|
+
t2.expects(:brief_description).returns("[internal] howdy")
|
95
|
+
t3 = task("e", "e", "")
|
96
|
+
t3.expects(:brief_description).returns("")
|
97
|
+
|
98
|
+
config = mock("config", :task_list => [t1, t2, t3])
|
99
|
+
@cli.options[:verbose] = 1
|
100
|
+
@cli.stubs(:puts)
|
101
|
+
@cli.expects(:puts).with { |s,| (s || "").include?("[internal]") || s =~ /#\s*$/ }.at_least_once
|
102
|
+
@cli.task_list(config)
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_explain_task_should_warn_if_task_does_not_exist
|
106
|
+
config = mock("config", :find_task => nil)
|
107
|
+
@cli.expects(:warn).with { |s,| s =~ /`deploy_with_niftiness'/ }
|
108
|
+
@cli.explain_task(config, "deploy_with_niftiness")
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_explain_task_with_task_that_has_no_description_should_emit_stub
|
112
|
+
t = mock("task", :description => "")
|
113
|
+
config = mock("config")
|
114
|
+
config.expects(:find_task).with("deploy_with_niftiness").returns(t)
|
115
|
+
@cli.stubs(:puts)
|
116
|
+
@cli.expects(:puts).with("There is no description for this task.")
|
117
|
+
@cli.explain_task(config, "deploy_with_niftiness")
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_explain_task_with_task_should_format_description
|
121
|
+
t = stub("task", :description => "line1\nline2\n\nline3")
|
122
|
+
config = mock("config", :find_task => t)
|
123
|
+
@cli.stubs(:puts)
|
124
|
+
@cli.explain_task(config, "deploy_with_niftiness")
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_long_help_should_load_and_format_help_txt_file
|
128
|
+
File.expects(:dirname).returns "a/b/c"
|
129
|
+
File.expects(:read).with("a/b/c/help.txt").returns("text")
|
130
|
+
@ui.expects(:say).with("text\n")
|
131
|
+
@cli.long_help
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
def task(name, fqn=name, desc="a description")
|
137
|
+
stub("task", :name => name, :fully_qualified_name => fqn, :description => desc)
|
138
|
+
end
|
139
|
+
end
|