capistrano 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. data/CHANGELOG +29 -0
  2. data/README +4 -7
  3. data/bin/cap +0 -0
  4. data/bin/capify +0 -0
  5. data/lib/capistrano/command.rb +32 -38
  6. data/lib/capistrano/configuration/actions/file_transfer.rb +21 -13
  7. data/lib/capistrano/configuration/actions/invocation.rb +1 -1
  8. data/lib/capistrano/configuration/connections.rb +30 -20
  9. data/lib/capistrano/errors.rb +1 -1
  10. data/lib/capistrano/processable.rb +53 -0
  11. data/lib/capistrano/recipes/deploy/remote_dependency.rb +6 -0
  12. data/lib/capistrano/recipes/deploy/scm/git.rb +26 -11
  13. data/lib/capistrano/recipes/deploy/scm/none.rb +44 -0
  14. data/lib/capistrano/recipes/deploy/strategy/base.rb +6 -0
  15. data/lib/capistrano/recipes/deploy/strategy/copy.rb +74 -3
  16. data/lib/capistrano/recipes/deploy.rb +15 -13
  17. data/lib/capistrano/role.rb +0 -14
  18. data/lib/capistrano/server_definition.rb +5 -0
  19. data/lib/capistrano/shell.rb +21 -17
  20. data/lib/capistrano/ssh.rb +24 -58
  21. data/lib/capistrano/transfer.rb +216 -0
  22. data/lib/capistrano/version.rb +1 -1
  23. data/test/cli/execute_test.rb +1 -1
  24. data/test/cli/help_test.rb +1 -1
  25. data/test/cli/options_test.rb +1 -1
  26. data/test/cli/ui_test.rb +1 -1
  27. data/test/cli_test.rb +1 -1
  28. data/test/command_test.rb +31 -51
  29. data/test/configuration/actions/file_transfer_test.rb +21 -19
  30. data/test/configuration/actions/inspect_test.rb +1 -1
  31. data/test/configuration/actions/invocation_test.rb +6 -6
  32. data/test/configuration/callbacks_test.rb +1 -1
  33. data/test/configuration/connections_test.rb +11 -12
  34. data/test/configuration/execution_test.rb +1 -1
  35. data/test/configuration/loading_test.rb +1 -1
  36. data/test/configuration/namespace_dsl_test.rb +1 -1
  37. data/test/configuration/roles_test.rb +1 -1
  38. data/test/configuration/servers_test.rb +1 -1
  39. data/test/configuration/variables_test.rb +1 -1
  40. data/test/configuration_test.rb +1 -1
  41. data/test/deploy/scm/accurev_test.rb +1 -1
  42. data/test/deploy/scm/base_test.rb +1 -1
  43. data/test/deploy/scm/git_test.rb +10 -6
  44. data/test/deploy/scm/mercurial_test.rb +1 -1
  45. data/test/deploy/strategy/copy_test.rb +120 -27
  46. data/test/extensions_test.rb +1 -1
  47. data/test/logger_test.rb +1 -1
  48. data/test/server_definition_test.rb +1 -1
  49. data/test/shell_test.rb +27 -1
  50. data/test/ssh_test.rb +27 -21
  51. data/test/task_definition_test.rb +1 -1
  52. data/test/transfer_test.rb +160 -0
  53. data/test/utils.rb +30 -34
  54. data/test/version_test.rb +1 -1
  55. metadata +26 -14
  56. data/lib/capistrano/gateway.rb +0 -131
  57. data/lib/capistrano/upload.rb +0 -152
  58. data/test/gateway_test.rb +0 -167
  59. data/test/upload_test.rb +0 -131
@@ -89,6 +89,15 @@ ensure
89
89
  ENV[name] = saved
90
90
  end
91
91
 
92
+ # If :run_method is :sudo (or :use_sudo is true), this executes the given command
93
+ # via +sudo+. Otherwise is uses +run+. Further, if sudo is being used and :runner
94
+ # is set, the command will be executed as the user given by :runner.
95
+ def try_sudo(command)
96
+ as = fetch(:runner, "app")
97
+ via = fetch(:run_method, :sudo)
98
+ invoke_command(command, :via => via, :as => as)
99
+ end
100
+
92
101
  # =========================================================================
93
102
  # These are the tasks that are available to help with deploying web apps,
94
103
  # and specifically, Rails applications. You can have cap give you a summary
@@ -122,7 +131,7 @@ namespace :deploy do
122
131
  task :setup, :except => { :no_release => true } do
123
132
  dirs = [deploy_to, releases_path, shared_path]
124
133
  dirs += %w(system log pids).map { |d| File.join(shared_path, d) }
125
- run "umask 02 && mkdir -p #{dirs.join(' ')}"
134
+ try_sudo "umask 02 && mkdir -p #{dirs.join(' ')}"
126
135
  end
127
136
 
128
137
  desc <<-DESC
@@ -246,9 +255,7 @@ namespace :deploy do
246
255
  set :use_sudo, false
247
256
  DESC
248
257
  task :restart, :roles => :app, :except => { :no_release => true } do
249
- as = fetch(:runner, "app")
250
- via = fetch(:run_method, :sudo)
251
- invoke_command "#{current_path}/script/process/reaper", :via => via, :as => as
258
+ try_sudo "#{current_path}/script/process/reaper"
252
259
  end
253
260
 
254
261
  desc <<-DESC
@@ -337,7 +344,7 @@ namespace :deploy do
337
344
  directories = (releases - releases.last(count)).map { |release|
338
345
  File.join(releases_path, release) }.join(" ")
339
346
 
340
- invoke_command "rm -rf #{directories}", :via => run_method
347
+ try_sudo "rm -rf #{directories}"
341
348
  end
342
349
  end
343
350
 
@@ -406,9 +413,7 @@ namespace :deploy do
406
413
  the :use_sudo variable to false.
407
414
  DESC
408
415
  task :start, :roles => :app do
409
- as = fetch(:runner, "app")
410
- via = fetch(:run_method, :sudo)
411
- invoke_command "sh -c 'cd #{current_path} && nohup script/spin'", :via => via, :as => as
416
+ try_sudo "sh -c 'cd #{current_path} && nohup script/spin'"
412
417
  end
413
418
 
414
419
  desc <<-DESC
@@ -423,11 +428,8 @@ namespace :deploy do
423
428
  the :use_sudo variable to false.
424
429
  DESC
425
430
  task :stop, :roles => :app do
426
- as = fetch(:runner, "app")
427
- via = fetch(:run_method, :sudo)
428
-
429
- invoke_command "if [ -f #{current_path}/tmp/pids/dispatch.spawner.pid ]; then #{current_path}/script/process/reaper -a kill -r dispatch.spawner.pid; fi", :via => via, :as => as
430
- invoke_command "#{current_path}/script/process/reaper -a kill", :via => via, :as => as
431
+ try_sudo "if [ -f #{current_path}/tmp/pids/dispatch.spawner.pid ]; then #{current_path}/script/process/reaper -a kill -r dispatch.spawner.pid; fi"
432
+ try_sudo "#{current_path}/script/process/reaper -a kill"
431
433
  end
432
434
 
433
435
  namespace :pending do
@@ -1,4 +1,3 @@
1
-
2
1
  module Capistrano
3
2
  class Role
4
3
  include Enumerable
@@ -34,27 +33,14 @@ module Capistrano
34
33
  servers.empty?
35
34
  end
36
35
 
37
- # Resets the cache, so that proc values may be recalculated.
38
- # There should be a command in Configuration::Roles to do this,
39
- # but I haven't needed it yet, and I'm not sure yet
40
- # what to call that command. Suggestions?
41
- def reset!
42
- @dynamic_servers.each { |item| item.reset! }
43
- end
44
-
45
- # Clears everything. I still thing this should be 'clear!', but that's not
46
- # the way Array does it.
47
36
  def clear
48
37
  @dynamic_servers.clear
49
38
  @static_servers.clear
50
39
  end
51
40
 
52
- # Mostly for documentation purposes. Doesn't seem to do anything.
53
41
  protected
54
42
 
55
43
  # This is the combination of a block, a hash of options, and a cached value.
56
- # It is protected because it is an implementation detail -- the original
57
- # implementation was two lists (blocks and cached results of calling them).
58
44
  class DynamicServerList
59
45
  def initialize (block, options)
60
46
  @block = block
@@ -7,6 +7,11 @@ module Capistrano
7
7
  attr_reader :port
8
8
  attr_reader :options
9
9
 
10
+ # The default user name to use when a user name is not explicitly provided
11
+ def self.default_user
12
+ ENV['USER'] || ENV['USERNAME'] || "not-specified"
13
+ end
14
+
10
15
  def initialize(string, options={})
11
16
  @user, @host, @port = string.match(/^(?:([^;,:=]+)@|)(.*?)(?::(\d+)|)$/)[1,3]
12
17
 
@@ -1,4 +1,5 @@
1
1
  require 'thread'
2
+ require 'capistrano/processable'
2
3
 
3
4
  module Capistrano
4
5
  # The Capistrano::Shell class is the guts of the "shell" task. It implements
@@ -6,6 +7,8 @@ module Capistrano
6
7
  # commands. It makes for a GREAT way to monitor systems, and perform quick
7
8
  # maintenance on one or more machines.
8
9
  class Shell
10
+ include Processable
11
+
9
12
  # A Readline replacement for platforms where readline is either
10
13
  # unavailable, or has not been installed.
11
14
  class ReadlineFallback #:nodoc:
@@ -142,11 +145,13 @@ HELP
142
145
  # be invoked. Otherwise, it is executed as a command on all associated
143
146
  # servers.
144
147
  def exec(command)
145
- if command[0] == ?!
146
- exec_tasks(command[1..-1].split)
147
- else
148
- servers = connect(configuration.current_task)
149
- exec_command(command, servers)
148
+ @mutex.synchronize do
149
+ if command[0] == ?!
150
+ exec_tasks(command[1..-1].split)
151
+ else
152
+ servers = connect(configuration.current_task)
153
+ exec_command(command, servers)
154
+ end
150
155
  end
151
156
  ensure
152
157
  STDOUT.flush
@@ -169,7 +174,8 @@ HELP
169
174
  command = command.gsub(/\bsudo\b/, "sudo -p '#{configuration.sudo_prompt}'")
170
175
  processor = configuration.sudo_behavior_callback(Configuration.default_io_proc)
171
176
  sessions = servers.map { |server| configuration.sessions[server] }
172
- cmd = Command.new(command, sessions, :logger => configuration.logger, &processor)
177
+ options = configuration.add_default_command_options({})
178
+ cmd = Command.new(command, sessions, options.merge(:logger => configuration.logger), &processor)
173
179
  previous = trap("INT") { cmd.stop! }
174
180
  cmd.process!
175
181
  rescue Capistrano::Error => error
@@ -196,17 +202,10 @@ HELP
196
202
 
197
203
  @mutex = Mutex.new
198
204
  @bgthread = Thread.new do
199
- loop do
200
- ready = configuration.sessions.values.select { |sess| sess.connection.reader_ready? }
201
- if ready.empty?
202
- sleep 0.1
203
- else
204
- @mutex.synchronize do
205
- ready.each { |session| session.connection.process(true) }
206
- end
207
- end
208
- end
205
+ loop do
206
+ @mutex.synchronize { process_iteration(0.1) }
209
207
  end
208
+ end
210
209
  end
211
210
 
212
211
  # Set the given option to +value+.
@@ -244,7 +243,7 @@ HELP
244
243
  old_var, ENV[env_var] = ENV[env_var], (scope_value == "all" ? nil : scope_value) if env_var
245
244
  if command
246
245
  begin
247
- @mutex.synchronize { exec(command) }
246
+ exec(command)
248
247
  ensure
249
248
  ENV[env_var] = old_var if env_var
250
249
  end
@@ -253,4 +252,9 @@ HELP
253
252
  end
254
253
  end
255
254
  end
255
+
256
+ # All open sessions, needed to satisfy the Command::Processable include
257
+ def sessions
258
+ configuration.sessions.values
259
+ end
256
260
  end
@@ -1,61 +1,12 @@
1
1
  begin
2
2
  require 'rubygems'
3
- gem 'net-ssh', "< 1.99.0"
3
+ gem 'net-ssh', ">= 1.99.1"
4
4
  rescue LoadError, NameError
5
5
  end
6
6
 
7
7
  require 'net/ssh'
8
8
 
9
9
  module Capistrano
10
- unless ENV['SKIP_VERSION_CHECK']
11
- require 'capistrano/version'
12
- require 'net/ssh/version'
13
- ssh_version = [Net::SSH::Version::MAJOR, Net::SSH::Version::MINOR, Net::SSH::Version::TINY]
14
- if !Version.check(Version::SSH_REQUIRED, ssh_version)
15
- raise "You have Net::SSH #{ssh_version.join(".")}, but you need at least #{Version::SSH_REQUIRED.join(".")}"
16
- end
17
- end
18
-
19
- # Now, Net::SSH is kind of silly, and tries to lazy-load everything. This
20
- # wreaks havoc with the parallel connection trick that Capistrano wants to
21
- # use, so we're going to do something hideously ugly here and force all the
22
- # files that Net::SSH uses to load RIGHT NOW, rather than lazily.
23
-
24
- net_ssh_dependencies = %w(connection/services connection/channel connection/driver
25
- service/agentforward/services service/agentforward/driver
26
- service/process/driver util/prompter
27
- service/forward/services service/forward/driver service/forward/local-network-handler service/forward/remote-network-handler
28
- service/shell/services service/shell/driver
29
- lenient-host-key-verifier
30
- transport/compress/services transport/compress/zlib-compressor transport/compress/none-compressor transport/compress/zlib-decompressor transport/compress/none-decompressor
31
- transport/kex/services transport/kex/dh transport/kex/dh-gex
32
- transport/ossl/services
33
- transport/ossl/hmac/services transport/ossl/hmac/sha1 transport/ossl/hmac/sha1-96 transport/ossl/hmac/md5 transport/ossl/hmac/md5-96 transport/ossl/hmac/none
34
- transport/ossl/cipher-factory transport/ossl/hmac-factory transport/ossl/buffer-factory transport/ossl/key-factory transport/ossl/digest-factory
35
- transport/identity-cipher transport/packet-stream transport/version-negotiator transport/algorithm-negotiator transport/session
36
- userauth/methods/services userauth/methods/password userauth/methods/keyboard-interactive userauth/methods/publickey userauth/methods/hostbased
37
- userauth/services userauth/agent userauth/userkeys userauth/driver
38
- transport/services service/services
39
- )
40
-
41
- net_ssh_dependencies << "userauth/pageant" if File::ALT_SEPARATOR
42
- net_ssh_dependencies.each do |path|
43
- begin
44
- require "net/ssh/#{path}"
45
- rescue LoadError
46
- # Ignore load errors from this, since some files are in the list which
47
- # do not exist in different (supported) versions of Net::SSH. We know
48
- # (by this point) that Net::SSH is installed, though, since we do a
49
- # require 'net/ssh' at the very top of this file, and we know the
50
- # installed version meets the minimum version requirements because of
51
- # the version check, also at the top of this file. So, if we get a
52
- # LoadError, it's simply because the file in question does not exist in
53
- # the version of Net::SSH that is installed.
54
- #
55
- # Whew!
56
- end
57
- end
58
-
59
10
  # A helper class for dealing with SSH connections.
60
11
  class SSH
61
12
  # Patch an accessor onto an SSH connection so that we can record the server
@@ -88,13 +39,30 @@ module Capistrano
88
39
  # constructor. Values in +options+ are then merged into it, and any
89
40
  # connection information in +server+ is added last, so that +server+ info
90
41
  # takes precedence over +options+, which takes precendence over ssh_options.
91
- def self.connect(server, options={}, &block)
42
+ def self.connect(server, options={})
43
+ connection_strategy(server, options) do |host, user, connection_options|
44
+ connection = Net::SSH.start(host, user, connection_options)
45
+ Server.apply_to(connection, server)
46
+ end
47
+ end
48
+
49
+ # Abstracts the logic for establishing an SSH connection (which includes
50
+ # testing for connection failures and retrying with a password, and so forth,
51
+ # mostly made complicated because of the fact that some of these variables
52
+ # might be lazily evaluated and try to do something like prompt the user,
53
+ # which should only happen when absolutely necessary.
54
+ #
55
+ # This will yield the hostname, username, and a hash of connection options
56
+ # to the given block, which should return a new connection.
57
+ def self.connection_strategy(server, options={}, &block)
92
58
  methods = [ %w(publickey hostbased), %w(password keyboard-interactive) ]
93
59
  password_value = nil
94
-
95
- ssh_options = (options[:ssh_options] || {}).dup
96
- ssh_options[:username] = server.user || options[:user] || ssh_options[:username]
97
- ssh_options[:port] = server.port || options[:port] || ssh_options[:port] || DEFAULT_PORT
60
+
61
+ ssh_options = (server.options[:ssh_options] || {}).merge(options[:ssh_options] || {})
62
+ user = server.user || options[:user] || ssh_options[:username] || ServerDefinition.default_user
63
+ ssh_options[:port] = server.port || options[:port] || ssh_options[:port] || DEFAULT_PORT
64
+
65
+ ssh_options.delete(:username)
98
66
 
99
67
  begin
100
68
  connection_options = ssh_options.merge(
@@ -102,9 +70,7 @@ module Capistrano
102
70
  :auth_methods => ssh_options[:auth_methods] || methods.shift
103
71
  )
104
72
 
105
- connection = Net::SSH.start(server.host, connection_options, &block)
106
- Server.apply_to(connection, server)
107
-
73
+ yield server.host, user, connection_options
108
74
  rescue Net::SSH::AuthenticationFailed
109
75
  raise if methods.empty? || ssh_options[:auth_methods]
110
76
  password_value = options[:password]
@@ -0,0 +1,216 @@
1
+ require 'net/scp'
2
+ require 'net/sftp'
3
+
4
+ require 'capistrano/processable'
5
+
6
+ module Capistrano
7
+ class Transfer
8
+ include Processable
9
+
10
+ def self.process(direction, from, to, sessions, options={}, &block)
11
+ new(direction, from, to, sessions, options, &block).process!
12
+ end
13
+
14
+ attr_reader :sessions
15
+ attr_reader :options
16
+ attr_reader :callback
17
+
18
+ attr_reader :transport
19
+ attr_reader :direction
20
+ attr_reader :from
21
+ attr_reader :to
22
+
23
+ attr_reader :logger
24
+ attr_reader :transfers
25
+
26
+ def initialize(direction, from, to, sessions, options={}, &block)
27
+ @direction = direction
28
+ @from = from
29
+ @to = to
30
+ @sessions = sessions
31
+ @options = options
32
+ @callback = callback
33
+
34
+ @transport = options.fetch(:via, :sftp)
35
+ @logger = options.delete(:logger)
36
+
37
+ @session_map = {}
38
+
39
+ prepare_transfers
40
+ end
41
+
42
+ def process!
43
+ loop do
44
+ begin
45
+ break unless process_iteration { active? }
46
+ rescue Exception => error
47
+ if error.respond_to?(:session)
48
+ handle_error(error)
49
+ else
50
+ raise
51
+ end
52
+ end
53
+ end
54
+
55
+ failed = transfers.select { |txfr| txfr[:failed] }
56
+ if failed.any?
57
+ hosts = failed.map { |txfr| txfr[:server] }
58
+ errors = failed.map { |txfr| "#{txfr[:error]} (#{txfr[:error].message})" }.uniq.join(", ")
59
+ error = TransferError.new("#{operation} via #{transport} failed on #{hosts.join(',')}: #{errors}")
60
+ error.hosts = hosts
61
+
62
+ logger.important(error.message) if logger
63
+ raise error
64
+ end
65
+
66
+ logger.debug "#{transport} #{operation} complete" if logger
67
+ self
68
+ end
69
+
70
+ def active?
71
+ transfers.any? { |transfer| transfer.active? }
72
+ end
73
+
74
+ def operation
75
+ "#{direction}load"
76
+ end
77
+
78
+ def sanitized_from
79
+ if from.responds_to?(:read)
80
+ "#<#{from.class}>"
81
+ else
82
+ from
83
+ end
84
+ end
85
+
86
+ def sanitized_to
87
+ if to.responds_to?(:read)
88
+ "#<#{to.class}>"
89
+ else
90
+ to
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def session_map
97
+ @session_map
98
+ end
99
+
100
+ def prepare_transfers
101
+ logger.info "#{transport} #{operation} #{from} -> #{to}" if logger
102
+
103
+ @transfers = sessions.map do |session|
104
+ session_from = normalize(from, session)
105
+ session_to = normalize(to, session)
106
+
107
+ session_map[session] = case transport
108
+ when :sftp
109
+ prepare_sftp_transfer(session_from, session_to, session)
110
+ when :scp
111
+ prepare_scp_transfer(session_from, session_to, session)
112
+ else
113
+ raise ArgumentError, "unsupported transport type: #{transport.inspect}"
114
+ end
115
+ end
116
+ end
117
+
118
+ def prepare_scp_transfer(from, to, session)
119
+ real_callback = callback || Proc.new do |channel, name, sent, total|
120
+ logger.trace "[#{channel[:host]}] #{name}" if logger && sent == 0
121
+ end
122
+
123
+ channel = case direction
124
+ when :up
125
+ session.scp.upload(from, to, options, &real_callback)
126
+ when :down
127
+ session.scp.download(from, to, options, &real_callback)
128
+ else
129
+ raise ArgumentError, "unsupported transfer direction: #{direction.inspect}"
130
+ end
131
+
132
+ channel[:server] = session.xserver
133
+ channel[:host] = session.xserver.host
134
+
135
+ return channel
136
+ end
137
+
138
+ class SFTPTransferWrapper
139
+ attr_reader :operation
140
+
141
+ def initialize(session, &callback)
142
+ session.sftp(false).connect do |sftp|
143
+ @operation = callback.call(sftp)
144
+ end
145
+ end
146
+
147
+ def active?
148
+ @operation.nil? || @operation.active?
149
+ end
150
+
151
+ def [](key)
152
+ @operation[key]
153
+ end
154
+
155
+ def []=(key, value)
156
+ @operation[key] = value
157
+ end
158
+
159
+ def abort!
160
+ @operation.abort!
161
+ end
162
+ end
163
+
164
+ def prepare_sftp_transfer(from, to, session)
165
+ SFTPTransferWrapper.new(session) do |sftp|
166
+ real_callback = Proc.new do |event, op, *args|
167
+ if callback
168
+ callback.call(event, op, *args)
169
+ elsif event == :open
170
+ logger.trace "[#{op[:host]}] #{args[0].remote}"
171
+ elsif event == :finish
172
+ logger.trace "[#{op[:host]}] done"
173
+ end
174
+ end
175
+
176
+ opts = options.dup
177
+ opts[:properties] = (opts[:properties] || {}).merge(
178
+ :server => session.xserver,
179
+ :host => session.xserver.host)
180
+
181
+ case direction
182
+ when :up
183
+ sftp.upload(from, to, opts, &real_callback)
184
+ when :down
185
+ sftp.download(from, to, opts, &real_callback)
186
+ else
187
+ raise ArgumentError, "unsupported transfer direction: #{direction.inspect}"
188
+ end
189
+ end
190
+ end
191
+
192
+ def normalize(argument, session)
193
+ if argument.is_a?(String)
194
+ argument.gsub(/\$CAPISTRANO:HOST\$/, session.xserver.host)
195
+ elsif argument.respond_to?(:read)
196
+ pos = argument.pos
197
+ clone = StringIO.new(argument.read)
198
+ clone.pos = argument.pos = pos
199
+ clone
200
+ else
201
+ argument
202
+ end
203
+ end
204
+
205
+ def handle_error(error)
206
+ transfer = session_map[error.session]
207
+ transfer[:error] = error
208
+ transfer[:failed] = true
209
+
210
+ case transport
211
+ when :sftp then transfer.abort!
212
+ when :scp then transfer.close
213
+ end
214
+ end
215
+ end
216
+ end
@@ -11,7 +11,7 @@ module Capistrano
11
11
  end
12
12
 
13
13
  MAJOR = 2
14
- MINOR = 2
14
+ MINOR = 3
15
15
  TINY = 0
16
16
 
17
17
  STRING = [MAJOR, MINOR, TINY].join(".")
@@ -1,4 +1,4 @@
1
- require "#{File.dirname(__FILE__)}/../utils"
1
+ require "utils"
2
2
  require 'capistrano/cli/execute'
3
3
 
4
4
  class CLIExecuteTest < Test::Unit::TestCase
@@ -1,4 +1,4 @@
1
- require "#{File.dirname(__FILE__)}/../utils"
1
+ require "utils"
2
2
  require 'capistrano/cli/help'
3
3
 
4
4
  class CLIHelpTest < Test::Unit::TestCase
@@ -1,4 +1,4 @@
1
- require "#{File.dirname(__FILE__)}/../utils"
1
+ require "utils"
2
2
  require 'capistrano/cli/options'
3
3
 
4
4
  class CLIOptionsTest < Test::Unit::TestCase
data/test/cli/ui_test.rb CHANGED
@@ -1,4 +1,4 @@
1
- require "#{File.dirname(__FILE__)}/../utils"
1
+ require "utils"
2
2
  require 'capistrano/cli/ui'
3
3
 
4
4
  class CLIUITest < Test::Unit::TestCase
data/test/cli_test.rb CHANGED
@@ -1,4 +1,4 @@
1
- require "#{File.dirname(__FILE__)}/utils"
1
+ require "utils"
2
2
  require 'capistrano/cli'
3
3
 
4
4
  class CLI_Test < Test::Unit::TestCase